From af0098038cfb63b8e1fb85fedc1446b81fd5b137 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Sat, 22 Aug 2026 15:03:17 +0200 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20Add=20program-format=20executio?= =?UTF-8?q?n=20feature=20metadata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a public per-format capability profile that distinguishes unknown, incomplete, and known-empty optional feature sets. Document the format baselines and cover the example-device property implementation. Assisted-by: GPT-5 via Codex --- CHANGELOG.md | 5 + UPGRADING.md | 44 +++++++ docs/examples.md | 28 +++++ examples/device/src/cxx_device.cpp | 13 ++ include/qdmi/constants.h | 186 +++++++++++++++++++++++++++-- test/test_qdmi.cpp | 37 ++++++ 6 files changed, 304 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d89302cd..9044730c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ clients compiled against a different minor or major version. ## [Unreleased] +### Added + +- ✨ Add optional, per-program-format atomic execution-feature metadata for QDMI + 1.4 that preserves incomplete and known-empty feature sets. + ## [1.3.3] - 2026-08-19 _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#133)._ diff --git a/UPGRADING.md b/UPGRADING.md index cbdaba0d..5df8642a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -7,6 +7,50 @@ releases, please refer to the ## [Unreleased] +### Program-format execution features + +This public-interface addition is intended for QDMI 1.4. Device plugins and +clients must use matching minor-version headers; a client must not query the new +property on a device implementation built against QDMI 1.3 headers. + +Devices may now expose atomic execution features for each supported program +format through the optional `QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES` +property. The property returns a list of `QDMI_Program_Format_Feature` records. +Existing devices may leave the property unsupported; clients must then treat +feature metadata as unknown. + +Each record contains a program format, one `QDMI_Program_Feature`, and an +`optional_features_complete` flag. Records with the same format form one feature +set and must agree on the flag. Every listed feature is supported. A zero flag +means the device may support additional optional features. A non-zero flag means +all features beyond those guaranteed by the program format are listed, so every +unlisted optional feature is unsupported. Format-inherent requirements remain +implicit and need not be repeated. + +Use `QDMI_PROGRAM_FEATURE_NONE` when the set has no listed feature. Together +with the completeness flag, this distinguishes incomplete metadata from a +known-empty feature set: + +```c +const QDMI_Program_Format_Feature unknown_qasm3 = { + QDMI_PROGRAM_FORMAT_QASM3, QDMI_PROGRAM_FEATURE_NONE, 0}; +const QDMI_Program_Format_Feature empty_qir_base = { + QDMI_PROGRAM_FORMAT_QIRBASESTRING, QDMI_PROGRAM_FEATURE_NONE, 1}; +``` + +A supported program format omitted from the returned records, a property query +returning `QDMI_ERROR_NOTSUPPORTED`, and a `QDMI_PROGRAM_FEATURE_NONE` record +with a zero completeness flag all denote unknown optional-feature metadata. +Clients must combine returned records with the requirements inherent to a +standardized format even when optional-feature metadata is incomplete or +unavailable. Text and binary enumerators have independent profiles; records for +one never apply to the other. + +Every standard format has an empty inherent feature baseline except QIR Adaptive +string and module programs. Both QIR Adaptive encodings inherently require +mid-circuit measurement, measured-qubit reuse, measurement-result use, Boolean +computation, and forward branching. Custom formats define their own baseline. + ## [1.3.3] ### Retrieving existing jobs by ID diff --git a/docs/examples.md b/docs/examples.md index 1e1e4b63..b5d4c41b 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -108,6 +108,34 @@ QDMI_Site pairs. The pairs are flattened into a single list of @ref QDMI_Site's. \until DOXYGEN FUNCTION END +### Program-Format Execution Features {#device-program-format-features} + +The optional @ref QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES property reports +atomic execution features separately for every supported program format. The +following example device reports two known OpenQASM 2 features while leaving the +optional-feature set incomplete. It reports no optional features beyond the +guarantees of either QIR Base encoding and omits its calibration format, so +optional-feature metadata for that format remains unknown. + + +\dontinclude cxx_device.cpp +\skipline constexpr std::array PROGRAM_FORMAT_FEATURES +\until }; +\skip int CXX_QDMI_device_session_query_device_property +\until { +\skip QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES +\until size_ret) + + +Each @ref QDMI_Program_Format_Feature record carries one atomic feature and a +`optional_features_complete` flag. A format with no listed optional feature uses +@ref QDMI_PROGRAM_FEATURE_NONE. For that sentinel record, a non-zero flag +denotes a known-empty optional-feature set; a zero flag says that additional +optional features may be supported. A nonzero flag on a real feature instead +denotes a complete nonempty set. A format without any record has unknown +optional-feature metadata. Requirements guaranteed by a standardized format +remain implicit and need not be repeated. + ### Complex Properties {#device-complex} The properties that are returned by @ref diff --git a/examples/device/src/cxx_device.cpp b/examples/device/src/cxx_device.cpp index cc73f5b7..aa3f369e 100644 --- a/examples/device/src/cxx_device.cpp +++ b/examples/device/src/cxx_device.cpp @@ -235,6 +235,16 @@ const std::unordered_map< constexpr std::array SUPPORTED_PROGRAM_FORMATS = { QDMI_PROGRAM_FORMAT_QASM2, QDMI_PROGRAM_FORMAT_QIRBASESTRING, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, QDMI_PROGRAM_FORMAT_CALIBRATION}; + +constexpr std::array PROGRAM_FORMAT_FEATURES = { + QDMI_Program_Format_Feature{QDMI_PROGRAM_FORMAT_QASM2, + QDMI_PROGRAM_FEATURE_MIDCIRCUITMEASUREMENT, 0}, + QDMI_Program_Format_Feature{QDMI_PROGRAM_FORMAT_QASM2, + QDMI_PROGRAM_FEATURE_MEASUREDQUBITREUSE, 0}, + QDMI_Program_Format_Feature{QDMI_PROGRAM_FORMAT_QIRBASESTRING, + QDMI_PROGRAM_FEATURE_NONE, 1}, + QDMI_Program_Format_Feature{QDMI_PROGRAM_FORMAT_QIRBASEMODULE, + QDMI_PROGRAM_FEATURE_NONE, 1}}; } // namespace // NOLINTBEGIN(bugprone-macro-parentheses) @@ -823,6 +833,9 @@ int CXX_QDMI_device_session_query_device_property( ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS, QDMI_Program_Format, SUPPORTED_PROGRAM_FORMATS, prop, size, value, size_ret) + ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES, + QDMI_Program_Format_Feature, PROGRAM_FORMAT_FEATURES, prop, + size, value, size_ret) return QDMI_ERROR_NOTSUPPORTED; } /// [DOXYGEN FUNCTION END] diff --git a/include/qdmi/constants.h b/include/qdmi/constants.h index 345727d1..be357126 100644 --- a/include/qdmi/constants.h +++ b/include/qdmi/constants.h @@ -18,8 +18,8 @@ */ /** @file - * @brief Defines all enums used within QDMI across the @ref client_interface - * and the @ref device_interface. + * @brief Defines constants and value types used within QDMI across the @ref + * client_interface and the @ref device_interface. */ #ifndef QDMI_CONSTANTS_H @@ -459,6 +459,42 @@ enum QDMI_DEVICE_PROPERTY_T { * cannot obtain a trustworthy queue length. */ QDMI_DEVICE_PROPERTY_QUEUELENGTH = 17, + /** + * @brief `QDMI_Program_Format_Feature*` (@ref + * QDMI_Program_Format_Feature list) Atomic execution features explicitly + * supported for individual program formats. + * @details Each record associates one @ref QDMI_Program_Feature with one + * format from @ref QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS. Records for + * the same format collectively describe that format's explicitly advertised + * execution profile. Their `optional_features_complete` fields must agree, + * and duplicate feature records are not allowed. + * @par + * Each @ref QDMI_Program_Format enumerator has an independent profile. In + * particular, records for a text representation do not apply to the + * corresponding binary representation, or vice versa. + * @par + * The execution semantics guaranteed by a standardized program format are + * inherent and need not be repeated in this property. Completeness applies + * only to additional features not guaranteed by the format. A format without + * a record has unknown optional-feature metadata. This is distinct from a + * known-empty optional-feature set, which is represented by exactly one + * record whose feature is @ref QDMI_PROGRAM_FEATURE_NONE and whose + * `optional_features_complete` field is non-zero. A record with + * @ref QDMI_PROGRAM_FEATURE_NONE and a zero `optional_features_complete` + * field explicitly reports that no optional feature is known while + * additional features may still be supported. + * @par + * When `optional_features_complete` is non-zero, an optional feature not + * listed for that exact format is unsupported. Format-inherent features + * remain supported even when they are not listed. + * @par + * If querying this property returns @ref QDMI_ERROR_NOTSUPPORTED, + * optional-feature metadata is unknown for every supported program format. + * Clients must combine the reported features with the requirements inherent + * to the program format regardless of whether optional-feature metadata is + * complete or available. + */ + QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES = 18, /** * @brief The maximum value of the enum. * @details It can be used by devices for bounds checking and validation of @@ -467,7 +503,7 @@ enum QDMI_DEVICE_PROPERTY_T { * @attention This value must remain the last regular member of the enum * besides the custom members and must be updated when new members are added. */ - QDMI_DEVICE_PROPERTY_MAX = 18, + QDMI_DEVICE_PROPERTY_MAX = 19, /** * @brief This enum value is reserved for a custom property. * @details The device defines the meaning and the type of this property. @@ -903,6 +939,11 @@ typedef enum QDMI_JOB_STATUS_T QDMI_Job_Status; /** * @brief Enum of formats that can be submitted to the device. + * @details The inherent atomic execution-feature baseline is empty for every + * standard format below except @ref QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING and + * @ref QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE. Each custom format defines its + * own baseline. Optional features are reported per exact enumerator through + * @ref QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES. */ enum QDMI_PROGRAM_FORMAT_T { /** @@ -910,7 +951,9 @@ enum QDMI_PROGRAM_FORMAT_T { * @details A text-based representation of a quantum circuit in the * [OpenQASM 2.0 language](https://arxiv.org/abs/1707.03429). Devices that * claim to support this format must accept programs conforming to the - * following rules: + * following rules and requiring only its inherent execution-feature baseline + * plus features advertised for @ref QDMI_PROGRAM_FORMAT_QASM2. The rules + * are: * - The program contains exactly one quantum register named `q`. * - The number of qubits in the quantum register `q` matches the number of * sites in the device. @@ -936,7 +979,9 @@ enum QDMI_PROGRAM_FORMAT_T { * @details A text-based representation of a quantum circuit in the * [OpenQASM 3 language](https://openqasm.com/). Devices that claim to support * this format must accept programs conforming to the same rules as for @ref - * QDMI_PROGRAM_FORMAT_QASM2. + * QDMI_PROGRAM_FORMAT_QASM2 and requiring only its inherent + * execution-feature baseline plus features advertised for @ref + * QDMI_PROGRAM_FORMAT_QASM3. * * @par * Besides the rules for OpenQASM 2.0 programs, OpenQASM 3 programs may @@ -960,10 +1005,12 @@ enum QDMI_PROGRAM_FORMAT_T { * Intermediate Representation (QIR) format; specifically, the [QIR base * profile](https://github.com/qir-alliance/qir-spec/blob/8b3fd47b7b70122a104e24733ef9de911576f7d6/specification/under_development/profiles/Base_Profile.md). * Devices that claim to support this format must accept programs that follow - * the rules for the QIR base profile and that only contain operations that - * are reported by the @ref QDMI_OPERATION_PROPERTY_NAME property of the - * device's operations (for example, `@__quantum__qis__[NAME]__body`, where - * `[NAME]` is the name of the operation). + * the rules for the QIR base profile, require only its empty inherent + * execution-feature baseline plus features advertised for @ref + * QDMI_PROGRAM_FORMAT_QIRBASESTRING, and only contain operations that are + * reported by the @ref QDMI_OPERATION_PROPERTY_NAME property of the device's + * operations (for example, `@__quantum__qis__[NAME]__body`, where `[NAME]` is + * the name of the operation). * * @par * QIR has a similar distinction between dynamically allocated and static @@ -988,6 +1035,9 @@ enum QDMI_PROGRAM_FORMAT_T { * @details A binary representation of a quantum circuit in the Quantum * Intermediate Representation (QIR) format; specifically, the [QIR base * profile](https://github.com/qir-alliance/qir-spec/blob/8b3fd47b7b70122a104e24733ef9de911576f7d6/specification/under_development/profiles/Base_Profile.md). + * Its inherent execution-feature baseline is empty, as for @ref + * QDMI_PROGRAM_FORMAT_QIRBASESTRING, while optional features are advertised + * independently for @ref QDMI_PROGRAM_FORMAT_QIRBASEMODULE. * * @see * QDMI_PROGRAM_FORMAT_QIRBASESTRING for more information on the QIR base @@ -1000,6 +1050,15 @@ enum QDMI_PROGRAM_FORMAT_T { * @details A text-based representation of a quantum circuit in the Quantum * Intermediate Representation (QIR) format; specifically, the [QIR adaptive * profile](https://github.com/qir-alliance/qir-spec/blob/8b3fd47b7b70122a104e24733ef9de911576f7d6/specification/under_development/profiles/Adaptive_Profile.md). + * Its inherent execution-feature baseline consists of @ref + * QDMI_PROGRAM_FEATURE_MIDCIRCUITMEASUREMENT, @ref + * QDMI_PROGRAM_FEATURE_MEASUREDQUBITREUSE, @ref + * QDMI_PROGRAM_FEATURE_MEASUREMENTRESULTUSE, @ref + * QDMI_PROGRAM_FEATURE_BOOLEANCOMPUTATION, and @ref + * QDMI_PROGRAM_FEATURE_FORWARDBRANCHING. Devices must accept programs that + * follow the QIR adaptive profile, only contain reported operations, and + * require only that baseline plus features advertised for @ref + * QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING. * * @see QDMI_PROGRAM_FORMAT_QIRBASESTRING for more information on the QIR base * profile and the expected behavior of devices supporting this format. @@ -1010,6 +1069,9 @@ enum QDMI_PROGRAM_FORMAT_T { * @details A binary representation of a quantum circuit in the Quantum * Intermediate Representation (QIR) format; specifically, the [QIR adaptive * profile](https://github.com/qir-alliance/qir-spec/blob/8b3fd47b7b70122a104e24733ef9de911576f7d6/specification/under_development/profiles/Adaptive_Profile.md). + * Its inherent execution-feature baseline is the same as for @ref + * QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, while optional features are + * advertised independently for @ref QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE. * * @see QDMI_PROGRAM_FORMAT_QIRBASESTRING for more information on the QIR base * profile and the expected behavior of devices supporting this format. @@ -1026,6 +1088,10 @@ enum QDMI_PROGRAM_FORMAT_T { * @brief `void*` A QPY program. * @details A binary representation of a Qiskit `QuantumCircuit` in the * [QPY format](https://quantum.cloud.ibm.com/docs/en/api/qiskit/qpy). + * Devices must accept circuits satisfying the structural rules described for + * @ref QDMI_PROGRAM_FORMAT_QASM3 and requiring only QPY's empty inherent + * execution-feature baseline plus features advertised for @ref + * QDMI_PROGRAM_FORMAT_QPY. * * @see QDMI_PROGRAM_FORMAT_QASM3 for more information on the expected * behavior of devices supporting this format. @@ -1087,6 +1153,108 @@ enum QDMI_PROGRAM_FORMAT_T { /// Program format type. typedef enum QDMI_PROGRAM_FORMAT_T QDMI_Program_Format; +/** + * @brief Enum of atomic execution features that a device can support for a + * program format. + * @details These features describe runtime semantics independently of the + * program syntax. A program may require several features, and a device + * advertises each optional feature separately for the applicable program + * format through @ref QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES. Features + * guaranteed by a standardized format remain implicit and need not be + * repeated. Support for one optional feature does not imply support for any + * other feature. + */ +enum QDMI_PROGRAM_FEATURE_T { + /** + * @brief No execution feature. + * @details This value is a metadata marker, not an execution feature. It + * allows a @ref QDMI_Program_Format_Feature record to carry completeness + * information when no supported feature is listed. + */ + QDMI_PROGRAM_FEATURE_NONE = 0, + /** + * @brief Measurement followed by further quantum execution or adaptive use + * of the result. + */ + QDMI_PROGRAM_FEATURE_MIDCIRCUITMEASUREMENT = 1, + /// Continued use of a qubit after it has been measured. + QDMI_PROGRAM_FEATURE_MEASUREDQUBITREUSE = 2, + /** + * @brief Runtime use of a measurement result beyond terminal reporting or + * return. + */ + QDMI_PROGRAM_FEATURE_MEASUREMENTRESULTUSE = 3, + /// Runtime Boolean computation. + QDMI_PROGRAM_FEATURE_BOOLEANCOMPUTATION = 4, + /// Runtime integer computation. + QDMI_PROGRAM_FEATURE_INTEGERCOMPUTATION = 5, + /// Runtime floating-point computation. + QDMI_PROGRAM_FEATURE_FLOATCOMPUTATION = 6, + /// Runtime conditional forward branching. + QDMI_PROGRAM_FEATURE_FORWARDBRANCHING = 7, + /// Runtime counted iteration. + QDMI_PROGRAM_FEATURE_COUNTEDITERATION = 8, + /// Runtime condition-terminated looping. + QDMI_PROGRAM_FEATURE_CONDITIONALLOOP = 9, + /// Runtime multiway branching. + QDMI_PROGRAM_FEATURE_MULTIWAYBRANCHING = 10, + /// Runtime definitions of and calls to IR-defined functions. + QDMI_PROGRAM_FEATURE_IRDEFINEDFUNCTIONS = 11, + /// Multiple return points in an entry point or IR-defined function. + QDMI_PROGRAM_FEATURE_MULTIPLERETURNPOINTS = 12, + /** + * @brief The maximum value of the enum. + * @details It can be used by devices for bounds checking and validation of + * feature records. + * + * @attention This value must remain the last regular member of the enum + * besides the custom members and must be updated when new members are added. + */ + QDMI_PROGRAM_FEATURE_MAX = 13, + /** + * @brief This enum value is reserved for a custom program feature. + * @details The device defines the meaning of this feature. + * @attention The value of this enum member must not be changed to maintain + * binary compatibility. + */ + QDMI_PROGRAM_FEATURE_CUSTOM1 = 999999995, + /// @see QDMI_PROGRAM_FEATURE_CUSTOM1 + QDMI_PROGRAM_FEATURE_CUSTOM2 = 999999996, + /// @see QDMI_PROGRAM_FEATURE_CUSTOM1 + QDMI_PROGRAM_FEATURE_CUSTOM3 = 999999997, + /// @see QDMI_PROGRAM_FEATURE_CUSTOM1 + QDMI_PROGRAM_FEATURE_CUSTOM4 = 999999998, + /// @see QDMI_PROGRAM_FEATURE_CUSTOM1 + QDMI_PROGRAM_FEATURE_CUSTOM5 = 999999999 +}; + +/// Program feature type. +typedef enum QDMI_PROGRAM_FEATURE_T QDMI_Program_Feature; + +/** + * @brief One atomic execution-feature record for a program format. + * @details All records for the same format must use the same value for @ref + * optional_features_complete. Every listed feature is known to be supported. + * A zero value means that additional optional features may also be supported. + * A non-zero value means that the records describe every supported feature not + * already guaranteed by the program format. + * @par + * When no feature is listed, a device may return one record with @ref feature + * set to @ref QDMI_PROGRAM_FEATURE_NONE. Together with @ref + * optional_features_complete, this distinguishes unknown optional-feature + * metadata from a known-empty optional-feature set. @ref + * QDMI_PROGRAM_FEATURE_NONE must not be combined with another record for the + * same format. + */ +typedef struct QDMI_PROGRAM_FORMAT_FEATURE_T { + /// Program format to which the feature metadata applies. + QDMI_Program_Format format; + /// Atomic execution feature, or @ref QDMI_PROGRAM_FEATURE_NONE. + QDMI_Program_Feature feature; + /// Whether all optional features for @ref format have been reported. + int optional_features_complete; +} QDMI_Program_Format_Feature; + /** * @brief Enum of the formats the results can be returned in. */ diff --git a/test/test_qdmi.cpp b/test/test_qdmi.cpp index 3bedab9b..e74218bf 100644 --- a/test/test_qdmi.cpp +++ b/test/test_qdmi.cpp @@ -407,6 +407,43 @@ TEST_P(QDMIImplementationTest, QueryDeviceProperties) { QDMI_SUCCESS); EXPECT_GE(scale_factor, 0.0); + // Query the format-scoped execution features. The example device reports an + // incomplete pair of OpenQASM 2 features, known-empty optional profiles for + // QIR Base, and no calibration metadata. + size = 0; + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES, 0, nullptr, + &size), + QDMI_SUCCESS); + std::vector format_features( + size / sizeof(QDMI_Program_Format_Feature)); + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES, size, + format_features.data(), nullptr), + QDMI_SUCCESS); + ASSERT_EQ(format_features.size(), 4); + EXPECT_EQ(format_features[0].format, QDMI_PROGRAM_FORMAT_QASM2); + EXPECT_EQ(format_features[0].feature, + QDMI_PROGRAM_FEATURE_MIDCIRCUITMEASUREMENT); + EXPECT_EQ(format_features[0].optional_features_complete, 0); + EXPECT_EQ(format_features[1].format, QDMI_PROGRAM_FORMAT_QASM2); + EXPECT_EQ(format_features[1].feature, + QDMI_PROGRAM_FEATURE_MEASUREDQUBITREUSE); + EXPECT_EQ(format_features[1].optional_features_complete, 0); + EXPECT_EQ(format_features[2].format, QDMI_PROGRAM_FORMAT_QIRBASESTRING); + EXPECT_EQ(format_features[3].format, QDMI_PROGRAM_FORMAT_QIRBASEMODULE); + EXPECT_EQ(format_features[2].feature, QDMI_PROGRAM_FEATURE_NONE); + EXPECT_NE(format_features[2].optional_features_complete, 0); + EXPECT_EQ(format_features[3].feature, QDMI_PROGRAM_FEATURE_NONE); + EXPECT_NE(format_features[3].optional_features_complete, 0); + EXPECT_TRUE(std::ranges::none_of(format_features, [](const auto &feature) { + return feature.format == QDMI_PROGRAM_FORMAT_CALIBRATION; + })); + EXPECT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES, size - 1, + format_features.data(), nullptr), + QDMI_ERROR_INVALIDARGUMENT); + // The example device does not support neutral atom-specific properties EXPECT_EQ( QDMI_device_query_device_property( From 2c7ab6f855694050efaff19af6dd99ad0fc8ad6c Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Sat, 22 Aug 2026 15:07:05 +0200 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9D=20Link=20capability=20metadata?= =?UTF-8?q?=20changelog=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the pull request and contributor references required by the QDMI changelog conventions. Assisted-by: GPT-5 via Codex --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9044730c..fd344939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ clients compiled against a different minor or major version. ### Added - ✨ Add optional, per-program-format atomic execution-feature metadata for QDMI - 1.4 that preserves incomplete and known-empty feature sets. + 1.4 that preserves incomplete and known-empty feature sets ([#508]) + ([\@simon1hofmann]). ## [1.3.3] - 2026-08-19 @@ -223,6 +224,7 @@ for previous changelogs._ +[#508]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/508 [#486]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/486 [#485]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/485 [#475]: https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/475 @@ -271,6 +273,7 @@ for previous changelogs._ +[@simon1hofmann]: https://github.com/simon1hofmann [@burgholzer]: https://github.com/burgholzer [@ystade]: https://github.com/ystade [@mnfarooqi]: https://github.com/mnfarooqi From 4356c0f893e5e143b1026758325c68739fdc82e3 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Sun, 23 Aug 2026 18:46:28 +0000 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=92=A5=20Define=20exact=20payload=20e?= =?UTF-8?q?xecution=20contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the legacy format enum with exact descriptors, descriptor-scoped optional features and typed constraints, and format-defined result ordering. Preserve the QIR 1 migration while separating newer QIR and output-schema versions. Assisted-by: GPT-5.6 Sol via Codex --- CHANGELOG.md | 21 +- UPGRADING.md | 137 +++-- cmake/prefix_defs.txt | 1 + cmake/test_defs.cpp.in | 2 + docs/examples.md | 34 +- examples/device/src/cxx_device.cpp | 207 ++++++-- examples/driver/qdmi_example_driver.cpp | 31 +- include/qdmi/client.h | 24 + include/qdmi/constants.h | 644 +++++++++++------------- include/qdmi/device.h | 25 + templates/device/src/my_device.cpp | 6 + test/CMakeLists.txt | 2 +- test/test_program_feature.c | 28 ++ test/test_qdmi.cpp | 314 ++++++++---- 14 files changed, 910 insertions(+), 566 deletions(-) create mode 100644 test/test_program_feature.c diff --git a/CHANGELOG.md b/CHANGELOG.md index fd344939..d83ec66b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,24 @@ clients compiled against a different minor or major version. ### Added -- ✨ Add optional, per-program-format atomic execution-feature metadata for QDMI - 1.4 that preserves incomplete and known-empty feature sets ([#508]) - ([\@simon1hofmann]). +- ✨ Add descriptor-scoped execution-feature queries and a format-defined + program-output result ([#508]) ([\@simon1hofmann], [\@burgholzer]). + +### Changed + +- 💥 Replace program-format enums with exact format descriptors and define the + flat-bit order of shot and histogram results ([#508]) ([\@simon1hofmann], + [\@burgholzer]). +- 💥 Require device libraries to export + `QDMI_device_session_retrieve_device_job_by_id`, while allowing the function + to return `QDMI_ERROR_NOTSUPPORTED` ([#508]) ([\@simon1hofmann], + [\@burgholzer]). + +### Removed + +- 💥 Remove calibration, batch-job, QPY, and IQM JSON values from the standard + program-format vocabulary. Providers can expose proprietary formats with a + namespaced descriptor ID ([#508]) ([\@simon1hofmann], [\@burgholzer]). ## [1.3.3] - 2026-08-19 diff --git a/UPGRADING.md b/UPGRADING.md index 5df8642a..1fd3343d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -9,47 +9,110 @@ releases, please refer to the ### Program-format execution features -This public-interface addition is intended for QDMI 1.4. Device plugins and -clients must use matching minor-version headers; a client must not query the new -property on a device implementation built against QDMI 1.3 headers. - -Devices may now expose atomic execution features for each supported program -format through the optional `QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES` -property. The property returns a list of `QDMI_Program_Format_Feature` records. -Existing devices may leave the property unsupported; clients must then treat -feature metadata as unknown. - -Each record contains a program format, one `QDMI_Program_Feature`, and an -`optional_features_complete` flag. Records with the same format form one feature -set and must agree on the flag. Every listed feature is supported. A zero flag -means the device may support additional optional features. A non-zero flag means -all features beyond those guaranteed by the program format are listed, so every -unlisted optional feature is unsupported. Format-inherent requirements remain -implicit and need not be repeated. - -Use `QDMI_PROGRAM_FEATURE_NONE` when the set has no listed feature. Together -with the completeness flag, this distinguishes incomplete metadata from a -known-empty feature set: +QDMI replaces the program-format enum with an exact `QDMI_Program_Format` +descriptor. The descriptor contains an ID, packed Semantic Versioning release, +profile, and text or binary encoding. Devices list every accepted descriptor in +`QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS`. Clients must submit one of those +exact values and must not infer version compatibility. ```c -const QDMI_Program_Format_Feature unknown_qasm3 = { - QDMI_PROGRAM_FORMAT_QASM3, QDMI_PROGRAM_FEATURE_NONE, 0}; -const QDMI_Program_Format_Feature empty_qir_base = { - QDMI_PROGRAM_FORMAT_QIRBASESTRING, QDMI_PROGRAM_FEATURE_NONE, 1}; +const QDMI_Program_Format qasm3 = { + QDMI_MAKE_VERSION(3, 0, 0), QDMI_PROGRAM_ENCODING_TEXT, "openqasm", ""}; ``` -A supported program format omitted from the returned records, a property query -returning `QDMI_ERROR_NOTSUPPORTED`, and a `QDMI_PROGRAM_FEATURE_NONE` record -with a zero completeness flag all denote unknown optional-feature metadata. -Clients must combine returned records with the requirements inherent to a -standardized format even when optional-feature metadata is incomplete or -unavailable. Text and binary enumerators have independent profiles; records for -one never apply to the other. - -Every standard format has an empty inherent feature baseline except QIR Adaptive -string and module programs. Both QIR Adaptive encodings inherently require -mid-circuit measurement, measured-qubit reuse, measurement-result use, Boolean -computation, and forward branching. Custom formats define their own baseline. +The size of a text payload includes its terminating NUL. Binary payloads remain +arbitrary byte sequences. + +QDMI reserves the unqualified IDs `openqasm` and `qir` for standard formats. +Vendor formats use namespaced IDs such as `com.vendor.format`. + +Use the following replacements for the removed enum values: + +| Removed value | `QDMI_Program_Format` replacement | +| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `QDMI_PROGRAM_FORMAT_QASM2` | `{QDMI_MAKE_VERSION(2, 0, 0), QDMI_PROGRAM_ENCODING_TEXT, "openqasm", ""}` | +| `QDMI_PROGRAM_FORMAT_QASM3` | `{QDMI_MAKE_VERSION(3, 0, 0), QDMI_PROGRAM_ENCODING_TEXT, "openqasm", ""}` | +| `QDMI_PROGRAM_FORMAT_QIRBASESTRING` | `{QDMI_MAKE_VERSION(1, 0, 0), QDMI_PROGRAM_ENCODING_TEXT, "qir", "base"}` | +| `QDMI_PROGRAM_FORMAT_QIRBASEMODULE` | `{QDMI_MAKE_VERSION(1, 0, 0), QDMI_PROGRAM_ENCODING_BINARY, "qir", "base"}` | +| `QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING` | `{QDMI_MAKE_VERSION(1, 0, 0), QDMI_PROGRAM_ENCODING_TEXT, "qir", "adaptive"}` | +| `QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE` | `{QDMI_MAKE_VERSION(1, 0, 0), QDMI_PROGRAM_ENCODING_BINARY, "qir", "adaptive"}` | +| `QDMI_PROGRAM_FORMAT_QPY` | No standard replacement; providers must document a vendor-namespaced descriptor | +| `QDMI_PROGRAM_FORMAT_IQMJSON` | A provider-defined descriptor with a namespaced ID | +| `QDMI_PROGRAM_FORMAT_CALIBRATION` | No descriptor; use a provider extension to trigger calibration | +| `QDMI_PROGRAM_FORMAT_BATCHJOB` | No descriptor; QDMI v1 has no portable multi-program submission API | +| `QDMI_PROGRAM_FORMAT_CUSTOM1` through `CUSTOM5` | A provider-defined descriptor with a namespaced ID | +| `QDMI_PROGRAM_FORMAT_MAX` | No replacement | + +QDMI defines no version, profile, wire, or result semantics for the removed QPY +and IQM JSON values. A provider can expose either format with a +vendor-namespaced descriptor and must document that descriptor and its payload +and result contract. + +The QIR replacements above preserve the QIR 1.0 meaning of the removed enum +values. A current QIR 2.1 descriptor instead uses `QDMI_MAKE_VERSION(2, 1, 0)`. +The descriptor version identifies the QIR specification and is independent of +the version in a QIR output-schema stream. + +Standard descriptors define exact mappings. They filter zone sites from +`QDMI_DEVICE_PROPERTY_SITES` and preserve the provider order of the remaining +regular sites. OpenQASM quantum instructions match a reported local operation by +name, qubit arity, and parameter arity. OpenQASM `q[i]` maps to the i-th regular +site, while OpenQASM 3 `$i` maps by `QDMI_SITE_PROPERTY_INDEX` to a regular +site. A QIR function named `__quantum__qis__NAME__body` matches the reported +local operation named `NAME`, with the same qubit and parameter arity. +Statically identified QIR qubits map by `QDMI_SITE_PROPERTY_INDEX` to regular +sites. A device that advertises QIR must assign the regular sites exactly the +index set `[0, N)`, where `N` is `QDMI_DEVICE_PROPERTY_QUBITSNUM`. Format- or +profile-mandated measurement, output, and runtime primitives do not need +corresponding operation records. The standard mappings do not define how a +payload selects a zoned operation. + +The new `QDMI_device_session_query_program_features` device function and +`QDMI_device_query_program_features` client function query optional execution +features for one exact descriptor. A successful query returns the complete list +of optional `QDMI_Program_Feature` records. A successful empty query means that +only the format's normative baseline is supported. `QDMI_ERROR_NOTSUPPORTED` +means that feature metadata is unknown. Boolean features use value zero. Width +features, such as integer computation, use one feature group per supported +width. A standard descriptor does not imply support for every construct that its +source format can express. + +Use `QDMI_PROGRAM_FEATURE_UNCONSTRAINED(id, value)` to initialize an +unrestricted feature without naming the constraint fields. Repeated records for +one feature and value add conjunctive typed constraints. QDMI initially defines +maximum control-flow nesting depth, loop iteration count, and multiway case +count. A client must treat a known feature group as unusable if any constraint +is unknown, malformed, duplicated, or not defined for that feature. + +Device plugins and clients must use matching minor-version headers. A QDMI 1.3 +client must not pass an enum value to a QDMI 1.4 device, and a QDMI 1.4 client +must not call the new query on a QDMI 1.3 device. + +Calibration and batch submission are no longer program formats. +`QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION` remains available, but QDMI does not +define a portable calibration trigger. QDMI v1 currently has no portable +multi-program submission API. + +`QDMI_JOB_RESULT_SHOTS` and histogram keys now describe payload-declared flat +bit outputs. OpenQASM 2 uses `creg` declarations in source order. OpenQASM 3 +uses explicit bit-valued outputs, or the language's implicit outputs when none +are declared. Both use increasing bit indices within each declaration. QIR uses +primitive result-recording call order. The result width is independent of the +device site count. A provider returns `QDMI_ERROR_NOTSUPPORTED` when an output +cannot be represented losslessly as a fixed-width bit string. +`QDMI_JOB_RESULT_PROGRAMOUTPUT` returns a format-defined output byte sequence +when a flat bit result cannot represent the payload result. A QIR specification +that defines an output schema uses a complete output-schema stream for every +shot. Older QIR or provider-defined descriptors can return +`QDMI_ERROR_NOTSUPPORTED`. The byte sequence need not be NUL-terminated. + +### Required device job retrieval symbol + +Every QDMI 1.4 device library must export +`QDMI_device_session_retrieve_device_job_by_id`. A device that cannot retrieve +jobs by ID returns `QDMI_ERROR_NOTSUPPORTED` from the function. Drivers no +longer accept a missing symbol. Add a stub implementation before rebuilding an +older device library against the QDMI 1.4 headers. ## [1.3.3] diff --git a/cmake/prefix_defs.txt b/cmake/prefix_defs.txt index 31a67379..95f9712c 100644 --- a/cmake/prefix_defs.txt +++ b/cmake/prefix_defs.txt @@ -15,6 +15,7 @@ QDMI_device_job_check QDMI_device_job_wait QDMI_device_job_get_results QDMI_device_session_query_device_property +QDMI_device_session_query_program_features QDMI_device_session_query_site_property QDMI_device_session_query_operation_property QDMI_Device_Session diff --git a/cmake/test_defs.cpp.in b/cmake/test_defs.cpp.in index 3b6bee3c..70fb5913 100644 --- a/cmake/test_defs.cpp.in +++ b/cmake/test_defs.cpp.in @@ -21,6 +21,7 @@ int main() { @QDMI_PREFIX@_QDMI_device_session_free(session); @QDMI_PREFIX@_QDMI_device_session_set_parameter(session, QDMI_DEVICE_SESSION_PARAMETER_MAX, 0, nullptr); @QDMI_PREFIX@_QDMI_device_session_create_device_job(session, &job); + @QDMI_PREFIX@_QDMI_device_session_retrieve_device_job_by_id(session, "", &job); @QDMI_PREFIX@_QDMI_device_job_free(job); @QDMI_PREFIX@_QDMI_device_job_set_parameter(job, QDMI_DEVICE_JOB_PARAMETER_MAX, 0, nullptr); @QDMI_PREFIX@_QDMI_device_job_query_property(job, QDMI_DEVICE_JOB_PROPERTY_MAX, 0, nullptr, nullptr); @@ -30,6 +31,7 @@ int main() { @QDMI_PREFIX@_QDMI_device_job_wait(job, 0); @QDMI_PREFIX@_QDMI_device_job_get_results(job, QDMI_JOB_RESULT_MAX, 0, nullptr, nullptr); @QDMI_PREFIX@_QDMI_device_session_query_device_property(session, QDMI_DEVICE_PROPERTY_MAX, 0, nullptr, nullptr); + @QDMI_PREFIX@_QDMI_device_session_query_program_features(session, nullptr, 0, nullptr, nullptr); @QDMI_PREFIX@_QDMI_device_session_query_site_property(session, site, QDMI_SITE_PROPERTY_MAX, 0, nullptr, nullptr); @QDMI_PREFIX@_QDMI_device_session_query_operation_property(session, operation, 0, nullptr, 0, nullptr, QDMI_OPERATION_PROPERTY_MAX, 0, nullptr, nullptr); } diff --git a/docs/examples.md b/docs/examples.md index b5d4c41b..82344d9b 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -110,31 +110,29 @@ QDMI_Site pairs. The pairs are flattened into a single list of @ref QDMI_Site's. ### Program-Format Execution Features {#device-program-format-features} -The optional @ref QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES property reports -atomic execution features separately for every supported program format. The -following example device reports two known OpenQASM 2 features while leaving the -optional-feature set incomplete. It reports no optional features beyond the -guarantees of either QIR Base encoding and omits its calibration format, so -optional-feature metadata for that format remains unknown. +The @ref QDMI_device_session_query_program_features function reports atomic +execution features for one exact program-format descriptor. The following +example device reports two unrestricted OpenQASM features and forward branching +with a maximum nesting depth of one. Its QIR Base descriptors return a +successful empty list because they support no optional feature beyond the QIR +Base baseline. \dontinclude cxx_device.cpp -\skipline constexpr std::array PROGRAM_FORMAT_FEATURES +\skipline QASM2_FEATURES{ \until }; -\skip int CXX_QDMI_device_session_query_device_property +\skip int CXX_QDMI_device_session_query_program_features \until { -\skip QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES -\until size_ret) +\until DOXYGEN FUNCTION END -Each @ref QDMI_Program_Format_Feature record carries one atomic feature and a -`optional_features_complete` flag. A format with no listed optional feature uses -@ref QDMI_PROGRAM_FEATURE_NONE. For that sentinel record, a non-zero flag -denotes a known-empty optional-feature set; a zero flag says that additional -optional features may be supported. A nonzero flag on a real feature instead -denotes a complete nonempty set. A format without any record has unknown -optional-feature metadata. Requirements guaranteed by a standardized format -remain implicit and need not be repeated. +Each @ref QDMI_Program_Feature record carries a feature ID, a feature-specific +value, and an optional typed constraint. Records for one feature and value form +one conjunctive group. An empty constraint ID means unrestricted support. Use +@ref QDMI_PROGRAM_FEATURE_UNCONSTRAINED to initialize such a record. Unknown or +malformed constraints make the group unusable. The returned list is complete. +Returning @ref QDMI_ERROR_NOTSUPPORTED keeps feature metadata unknown. +Requirements guaranteed by a standard descriptor remain implicit. ### Complex Properties {#device-complex} diff --git a/examples/device/src/cxx_device.cpp b/examples/device/src/cxx_device.cpp index aa3f369e..2e655ffa 100644 --- a/examples/device/src/cxx_device.cpp +++ b/examples/device/src/cxx_device.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -63,7 +64,7 @@ struct CXX_QDMI_Device_Session_impl_d { struct CXX_QDMI_Device_Job_impl_d { CXX_QDMI_Device_Session session = nullptr; int id = 0; - QDMI_Program_Format format = QDMI_PROGRAM_FORMAT_MAX; + QDMI_Program_Format format{}; void *program = nullptr; QDMI_Job_Status status = QDMI_JOB_STATUS_SUBMITTED; size_t num_shots = 0; @@ -232,19 +233,92 @@ const std::unordered_map< // No need to specify single-qubit fidelities here }; +constexpr QDMI_Program_Format QASM2_FORMAT{ + .version = QDMI_MAKE_VERSION(2, 0, 0), + .encoding = QDMI_PROGRAM_ENCODING_TEXT, + .id = "openqasm", + .profile = ""}; +constexpr QDMI_Program_Format QIR_BASE_TEXT_FORMAT{ + .version = QDMI_MAKE_VERSION(2, 1, 0), + .encoding = QDMI_PROGRAM_ENCODING_TEXT, + .id = "qir", + .profile = "base"}; +constexpr QDMI_Program_Format QIR_BASE_BINARY_FORMAT{ + .version = QDMI_MAKE_VERSION(2, 1, 0), + .encoding = QDMI_PROGRAM_ENCODING_BINARY, + .id = "qir", + .profile = "base"}; + constexpr std::array SUPPORTED_PROGRAM_FORMATS = { - QDMI_PROGRAM_FORMAT_QASM2, QDMI_PROGRAM_FORMAT_QIRBASESTRING, - QDMI_PROGRAM_FORMAT_QIRBASEMODULE, QDMI_PROGRAM_FORMAT_CALIBRATION}; - -constexpr std::array PROGRAM_FORMAT_FEATURES = { - QDMI_Program_Format_Feature{QDMI_PROGRAM_FORMAT_QASM2, - QDMI_PROGRAM_FEATURE_MIDCIRCUITMEASUREMENT, 0}, - QDMI_Program_Format_Feature{QDMI_PROGRAM_FORMAT_QASM2, - QDMI_PROGRAM_FEATURE_MEASUREDQUBITREUSE, 0}, - QDMI_Program_Format_Feature{QDMI_PROGRAM_FORMAT_QIRBASESTRING, - QDMI_PROGRAM_FEATURE_NONE, 1}, - QDMI_Program_Format_Feature{QDMI_PROGRAM_FORMAT_QIRBASEMODULE, - QDMI_PROGRAM_FEATURE_NONE, 1}}; + QASM2_FORMAT, QIR_BASE_TEXT_FORMAT, QIR_BASE_BINARY_FORMAT}; + +constexpr std::array QASM2_FEATURES{ + {QDMI_PROGRAM_FEATURE_UNCONSTRAINED( + QDMI_PROGRAM_FEATURE_MID_CIRCUIT_MEASUREMENT, 0), + QDMI_PROGRAM_FEATURE_UNCONSTRAINED( + QDMI_PROGRAM_FEATURE_MEASURED_QUBIT_REUSE, 0), + QDMI_Program_Feature{ + .id = QDMI_PROGRAM_FEATURE_FORWARD_BRANCHING, + .value = 0, + .constraint_id = + QDMI_PROGRAM_CONSTRAINT_MAX_CONTROL_FLOW_NESTING_DEPTH, + .constraint_value = 1}}}; + +constexpr std::string_view FLAT_SHOT_OUTPUT{"01"}; +constexpr std::string_view QIR_PROGRAM_OUTPUT_HEADER = + "HEADER\tschema_id\tordered\n" + "HEADER\tschema_version\t2.1\n"; +constexpr std::string_view QIR_PROGRAM_OUTPUT_SHOT = + "START\n" + "METADATA\tentry_point\n" + "METADATA\tqir_profiles\tbase_profile\n" + "METADATA\toutput_labeling_schema\tschema_id\n" + "METADATA\trequired_num_qubits\t2\n" + "METADATA\trequired_num_results\t2\n" + "OUTPUT\tRESULT\t0\n" + "OUTPUT\tRESULT\t1\n" + "END\t0\n"; + +[[nodiscard]] bool Same_format(const QDMI_Program_Format &lhs, + const QDMI_Program_Format &rhs) { + return lhs.version == rhs.version && lhs.encoding == rhs.encoding && + std::ranges::equal(lhs.id, rhs.id) && + std::ranges::equal(lhs.profile, rhs.profile); +} + +[[nodiscard]] bool Valid_format(const QDMI_Program_Format &format) { + const auto canonical = [](const auto &text) { + const auto nul = std::ranges::find(text, '\0'); + return nul != std::end(text) && + std::ranges::all_of(nul, std::end(text), + [](const char value) { return value == '\0'; }); + }; + if (format.version == 0U || + (format.encoding != QDMI_PROGRAM_ENCODING_TEXT && + format.encoding != QDMI_PROGRAM_ENCODING_BINARY) || + !canonical(format.id) || format.id[0] == '\0' || + !canonical(format.profile)) { + return false; + } + + const std::string_view id{format.id}; + const std::string_view profile{format.profile}; + if (id == "openqasm") { + return format.encoding == QDMI_PROGRAM_ENCODING_TEXT && profile.empty(); + } + if (id == "qir") { + return profile == "base" || profile == "adaptive"; + } + return id.front() != '.' && id.back() != '.' && + id.find('.') != std::string_view::npos && + id.find("..") == std::string_view::npos; +} + +[[nodiscard]] bool Supported_format(const QDMI_Program_Format &format) { + return std::ranges::any_of( + SUPPORTED_PROGRAM_FORMATS, + [&](const auto &candidate) { return Same_format(format, candidate); }); +} } // namespace // NOLINTBEGIN(bugprone-macro-parentheses) @@ -415,19 +489,14 @@ int CXX_QDMI_device_job_set_parameter(CXX_QDMI_Device_Job job, switch (param) { case QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT: if (value != nullptr) { + if (size != sizeof(QDMI_Program_Format)) { + return QDMI_ERROR_INVALIDARGUMENT; + } 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 (!Valid_format(format)) { return QDMI_ERROR_INVALIDARGUMENT; } - if (format != QDMI_PROGRAM_FORMAT_QASM2 && - format != QDMI_PROGRAM_FORMAT_QIRBASESTRING && - format != QDMI_PROGRAM_FORMAT_QIRBASEMODULE && - format != QDMI_PROGRAM_FORMAT_CALIBRATION) { + if (!Supported_format(format)) { return QDMI_ERROR_NOTSUPPORTED; } job->format = format; @@ -478,12 +547,6 @@ int CXX_QDMI_device_job_submit(CXX_QDMI_Device_Job job) { return QDMI_ERROR_INVALIDARGUMENT; } - // Calibration jobs complete immediately - if (job->format == QDMI_PROGRAM_FORMAT_CALIBRATION) { - job->status = QDMI_JOB_STATUS_DONE; - return QDMI_SUCCESS; - } - CXX_QDMI_set_device_status(QDMI_DEVICE_STATUS_BUSY); job->status = QDMI_JOB_STATUS_SUBMITTED; // here, the actual submission of the problem to the device would happen @@ -496,14 +559,7 @@ int CXX_QDMI_device_job_submit(CXX_QDMI_Device_Job job) { job->session, QDMI_DEVICE_PROPERTY_QUBITSNUM, sizeof(size_t), &num_qubits, nullptr); job->results.clear(); - job->results.reserve(job->num_shots); - for (size_t i = 0; i < job->num_shots; ++i) { - // generate random bitstring - std::string result(num_qubits, '0'); - std::ranges::generate( - result, [&]() { return CXX_QDMI_generate_bit() ? '1' : '0'; }); - job->results.emplace_back(std::move(result)); - } + job->results.assign(job->num_shots, std::string{FLAT_SHOT_OUTPUT}); // Generate random complex numbers and calculate the norm job->state_vec.clear(); job->state_vec.reserve(1U << num_qubits); @@ -739,6 +795,39 @@ int CXX_QDMI_device_job_get_results_probabilities(CXX_QDMI_Device_Job job, } return QDMI_SUCCESS; } /// [DOXYGEN FUNCTION END] + +int CXX_QDMI_device_job_get_program_output(CXX_QDMI_Device_Job job, + const size_t size, void *data, + size_t *size_ret) { + if (!std::ranges::equal(job->format.id, QIR_BASE_TEXT_FORMAT.id)) { + return QDMI_ERROR_NOTSUPPORTED; + } + if (job->num_shots > + (std::numeric_limits::max() - QIR_PROGRAM_OUTPUT_HEADER.size()) / + QIR_PROGRAM_OUTPUT_SHOT.size()) { + return QDMI_ERROR_FATAL; + } + const size_t required = QIR_PROGRAM_OUTPUT_HEADER.size() + + (job->num_shots * QIR_PROGRAM_OUTPUT_SHOT.size()); + if (size_ret != nullptr) { + *size_ret = required; + } + if (data != nullptr) { + if (size < required) { + return QDMI_ERROR_INVALIDARGUMENT; + } + auto *output = static_cast(data); + std::memcpy(output, QIR_PROGRAM_OUTPUT_HEADER.data(), + QIR_PROGRAM_OUTPUT_HEADER.size()); + output += QIR_PROGRAM_OUTPUT_HEADER.size(); + for (size_t shot = 0; shot < job->num_shots; ++shot) { + std::memcpy(output, QIR_PROGRAM_OUTPUT_SHOT.data(), + QIR_PROGRAM_OUTPUT_SHOT.size()); + output += QIR_PROGRAM_OUTPUT_SHOT.size(); + } + } + return QDMI_SUCCESS; +} /// [DOXYGEN FUNCTION END] } // namespace int CXX_QDMI_device_job_get_results(CXX_QDMI_Device_Job job, @@ -772,6 +861,8 @@ int CXX_QDMI_device_job_get_results(CXX_QDMI_Device_Job job, case QDMI_JOB_RESULT_PROBABILITIES_DENSE: return CXX_QDMI_device_job_get_results_probabilities(job, size, data, size_ret); + case QDMI_JOB_RESULT_PROGRAMOUTPUT: + return CXX_QDMI_device_job_get_program_output(job, size, data, size_ret); default: return QDMI_ERROR_NOTSUPPORTED; } @@ -812,7 +903,7 @@ int CXX_QDMI_device_session_query_device_property( ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_COUPLINGMAP, CXX_QDMI_Site, DEVICE_COUPLING_MAP, prop, size, value, size_ret) - // The example device never requires calibration + // The example device never requires calibration. ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION, size_t, 0, prop, size, value, size_ret) @@ -833,13 +924,45 @@ int CXX_QDMI_device_session_query_device_property( ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS, QDMI_Program_Format, SUPPORTED_PROGRAM_FORMATS, prop, size, value, size_ret) - ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES, - QDMI_Program_Format_Feature, PROGRAM_FORMAT_FEATURES, prop, - size, value, size_ret) - return QDMI_ERROR_NOTSUPPORTED; } /// [DOXYGEN FUNCTION END] +int CXX_QDMI_device_session_query_program_features( + CXX_QDMI_Device_Session session, const QDMI_Program_Format *format, + const size_t size, QDMI_Program_Feature *value, size_t *size_ret) { + if (session == nullptr || format == nullptr || !Valid_format(*format)) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (session->status != CXX_QDMI_DEVICE_SESSION_STATUS::INITIALIZED) { + return QDMI_ERROR_BADSTATE; + } + if (!Supported_format(*format)) { + return QDMI_ERROR_NOTSUPPORTED; + } + + const auto copy = [&](const auto &features) { + const size_t required = sizeof(features); + if (size_ret != nullptr) { + *size_ret = required; + } + if (value == nullptr) { + return QDMI_SUCCESS; + } + if (size < required) { + return QDMI_ERROR_INVALIDARGUMENT; + } + std::ranges::copy(features, value); + return QDMI_SUCCESS; + }; + if (Same_format(*format, QASM2_FORMAT)) { + return copy(QASM2_FEATURES); + } + if (size_ret != nullptr) { + *size_ret = 0U; + } + return QDMI_SUCCESS; +} /// [DOXYGEN FUNCTION END] + int CXX_QDMI_device_session_query_site_property(CXX_QDMI_Device_Session session, CXX_QDMI_Site site, const QDMI_Site_Property prop, diff --git a/examples/driver/qdmi_example_driver.cpp b/examples/driver/qdmi_example_driver.cpp index a19a4f76..079b03dd 100644 --- a/examples/driver/qdmi_example_driver.cpp +++ b/examples/driver/qdmi_example_driver.cpp @@ -100,6 +100,9 @@ struct QDMI_Library { /// Function pointer to @ref QDMI_device_session_query_device_property. decltype(QDMI_device_session_query_device_property) *device_session_query_device_property{}; + /// Function pointer to @ref QDMI_device_session_query_program_features. + decltype(QDMI_device_session_query_program_features) + *device_session_query_program_features{}; /// Function pointer to @ref QDMI_device_session_query_site_property. decltype(QDMI_device_session_query_site_property) *device_session_query_site_property{}; @@ -181,13 +184,6 @@ QDMI_Driver_State *QDMI_get_driver_state() { } \ } -#define LOAD_OPTIONAL_SYMBOL(device, prefix, symbol) \ - { \ - const std::string symbol_name = std::string(prefix) + "_QDMI_" + #symbol; \ - (device).symbol = reinterpret_cast( \ - dlsym((device).lib_handle, symbol_name.c_str())); \ - } - void QDMI_library_load(const std::string &lib_name, const std::string &prefix) { auto *lib_handle = dlopen(lib_name.c_str(), RTLD_NOW | RTLD_LOCAL); if (lib_handle == nullptr) { @@ -218,8 +214,7 @@ void QDMI_library_load(const std::string &lib_name, const std::string &prefix) { LOAD_SYMBOL(library, prefix, device_session_set_parameter) // device job interface LOAD_SYMBOL(library, prefix, device_session_create_device_job) - LOAD_OPTIONAL_SYMBOL(library, prefix, - device_session_retrieve_device_job_by_id) + LOAD_SYMBOL(library, prefix, device_session_retrieve_device_job_by_id) LOAD_SYMBOL(library, prefix, device_job_free) LOAD_SYMBOL(library, prefix, device_job_set_parameter) LOAD_SYMBOL(library, prefix, device_job_query_property) @@ -230,6 +225,7 @@ void QDMI_library_load(const std::string &lib_name, const std::string &prefix) { LOAD_SYMBOL(library, prefix, device_job_get_results) // device query interface LOAD_SYMBOL(library, prefix, device_session_query_device_property) + LOAD_SYMBOL(library, prefix, device_session_query_program_features) LOAD_SYMBOL(library, prefix, device_session_query_site_property) LOAD_SYMBOL(library, prefix, device_session_query_operation_property) @@ -480,11 +476,6 @@ int QDMI_session_retrieve_job_by_id(QDMI_Device dev, const char *job_id, return QDMI_ERROR_PERMISSIONDENIED; } - if (dev->library->device_session_retrieve_device_job_by_id == nullptr) { - // This compatibility path is only reachable for pre-1.3.3 binaries. - return QDMI_ERROR_NOTSUPPORTED; // LCOV_EXCL_LINE - } - auto retrieved_job = std::make_unique(); retrieved_job->device = dev; const auto status = dev->library->device_session_retrieve_device_job_by_id( @@ -574,6 +565,18 @@ int QDMI_device_query_device_property(QDMI_Device device, device->device_session, prop, size, value, size_ret); } +int QDMI_device_query_program_features(QDMI_Device device, + const QDMI_Program_Format *format, + const size_t size, + QDMI_Program_Feature *value, + size_t *size_ret) { + if (device == nullptr || format == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return device->library->device_session_query_program_features( + device->device_session, format, size, value, size_ret); +} + int QDMI_device_query_site_property(QDMI_Device device, QDMI_Site site, QDMI_Site_Property prop, const size_t size, void *value, size_t *size_ret) { diff --git a/include/qdmi/client.h b/include/qdmi/client.h index 90ec9bac..62454d29 100644 --- a/include/qdmi/client.h +++ b/include/qdmi/client.h @@ -438,6 +438,30 @@ int QDMI_device_query_device_property(QDMI_Device device, QDMI_Device_Property prop, size_t size, void *value, size_t *size_ret); +/** + * @brief Query the complete optional feature guarantees for one exact program + * format. + * @param[in] device The device to query. Must not be @c NULL. + * @param[in] format A descriptor returned by @ref + * QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS. Must not be @c NULL. + * @param[in] size The size of @p value in bytes. Ignored when @p value is + * @c NULL. + * @param[out] value Storage for a list of @ref QDMI_Program_Feature records, + * including all constraints, or @c NULL to query the required size. + * @param[out] size_ret The required list size in bytes, or @c NULL. + * @return @ref QDMI_SUCCESS when the complete optional set is available. A + * zero-byte result means that the format supports only its normative baseline. + * @return @ref QDMI_ERROR_NOTSUPPORTED when optional feature metadata is + * unknown for @p format. + * @return @ref QDMI_ERROR_INVALIDARGUMENT for a null handle or descriptor, an + * invalid descriptor, or an insufficient output buffer. + * @return @ref QDMI_ERROR_FATAL if an unexpected error occurred. + */ +int QDMI_device_query_program_features(QDMI_Device device, + const QDMI_Program_Format *format, + size_t size, QDMI_Program_Feature *value, + size_t *size_ret); + /** * @brief Query a site property. * @param[in] device The device to query. Must not be @c NULL. diff --git a/include/qdmi/constants.h b/include/qdmi/constants.h index be357126..73715196 100644 --- a/include/qdmi/constants.h +++ b/include/qdmi/constants.h @@ -25,6 +25,8 @@ #ifndef QDMI_CONSTANTS_H #define QDMI_CONSTANTS_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -347,14 +349,10 @@ enum QDMI_DEVICE_PROPERTY_T { QDMI_DEVICE_PROPERTY_COUPLINGMAP = 7, /** * @brief `size_t` Whether the device needs calibration. - * @details This flag indicates whether the device needs calibration. - * A value of zero indicates that the device does not need calibration, while - * any non-zero value indicates that the device needs calibration. It is up - * to the device to assign a specific meaning to the non-zero value. - * - * If a device reports that it needs calibration, a calibration run can be - * triggered by submitting a job with the @ref QDMI_Program_Format set to @ref - * QDMI_PROGRAM_FORMAT_CALIBRATION. + * @details Zero means that the device does not need calibration. A nonzero + * value means that the device needs calibration. The device defines the + * meaning of each nonzero value. QDMI does not define a portable way to + * trigger calibration. */ QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION = 8, /** @@ -423,11 +421,12 @@ enum QDMI_DEVICE_PROPERTY_T { */ QDMI_DEVICE_PROPERTY_MINATOMDISTANCE = 14, /** - * @brief `QDMI_Program_Format*` (@ref QDMI_Program_Format list) The program - * formats supported by the device. - * @details The returned list contains all program formats that the device - * supports for execution. A client can use this information to determine - * which program formats can be used when submitting jobs to the device. + * @brief `QDMI_Program_Format*` (@ref QDMI_Program_Format list) The exact + * program formats supported by the device. + * @details Every returned descriptor can be passed unchanged as the job's + * program-format parameter. Descriptors with different versions, profiles, + * or encodings are independent formats. The list is ordered from most to + * least preferred by the device provider. */ QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS = 15, /** @@ -459,42 +458,6 @@ enum QDMI_DEVICE_PROPERTY_T { * cannot obtain a trustworthy queue length. */ QDMI_DEVICE_PROPERTY_QUEUELENGTH = 17, - /** - * @brief `QDMI_Program_Format_Feature*` (@ref - * QDMI_Program_Format_Feature list) Atomic execution features explicitly - * supported for individual program formats. - * @details Each record associates one @ref QDMI_Program_Feature with one - * format from @ref QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS. Records for - * the same format collectively describe that format's explicitly advertised - * execution profile. Their `optional_features_complete` fields must agree, - * and duplicate feature records are not allowed. - * @par - * Each @ref QDMI_Program_Format enumerator has an independent profile. In - * particular, records for a text representation do not apply to the - * corresponding binary representation, or vice versa. - * @par - * The execution semantics guaranteed by a standardized program format are - * inherent and need not be repeated in this property. Completeness applies - * only to additional features not guaranteed by the format. A format without - * a record has unknown optional-feature metadata. This is distinct from a - * known-empty optional-feature set, which is represented by exactly one - * record whose feature is @ref QDMI_PROGRAM_FEATURE_NONE and whose - * `optional_features_complete` field is non-zero. A record with - * @ref QDMI_PROGRAM_FEATURE_NONE and a zero `optional_features_complete` - * field explicitly reports that no optional feature is known while - * additional features may still be supported. - * @par - * When `optional_features_complete` is non-zero, an optional feature not - * listed for that exact format is unsupported. Format-inherent features - * remain supported even when they are not listed. - * @par - * If querying this property returns @ref QDMI_ERROR_NOTSUPPORTED, - * optional-feature metadata is unknown for every supported program format. - * Clients must combine the reported features with the requirements inherent - * to the program format regardless of whether optional-feature metadata is - * complete or available. - */ - QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES = 18, /** * @brief The maximum value of the enum. * @details It can be used by devices for bounds checking and validation of @@ -503,7 +466,7 @@ enum QDMI_DEVICE_PROPERTY_T { * @attention This value must remain the last regular member of the enum * besides the custom members and must be updated when new members are added. */ - QDMI_DEVICE_PROPERTY_MAX = 19, + QDMI_DEVICE_PROPERTY_MAX = 18, /** * @brief This enum value is reserved for a custom property. * @details The device defines the meaning and the type of this property. @@ -937,323 +900,254 @@ enum QDMI_JOB_STATUS_T { /// Job status type. typedef enum QDMI_JOB_STATUS_T QDMI_Job_Status; +/// Maximum bytes, including the terminating NUL, in a format or profile ID. +#define QDMI_PROGRAM_ID_SIZE 64U + /** - * @brief Enum of formats that can be submitted to the device. - * @details The inherent atomic execution-feature baseline is empty for every - * standard format below except @ref QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING and - * @ref QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE. Each custom format defines its - * own baseline. Optional features are reported per exact enumerator through - * @ref QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES. + * @brief Pack a Semantic Versioning major, minor, and patch release into a + * 32-bit exact version value. + * @details The major and minor components must each fit in 10 bits. The patch + * component must fit in 12 bits. Prerelease and build metadata are not part of + * a program-format descriptor. */ -enum QDMI_PROGRAM_FORMAT_T { - /** - * @brief `char*` (string) An OpenQASM 2.0 program. - * @details A text-based representation of a quantum circuit in the - * [OpenQASM 2.0 language](https://arxiv.org/abs/1707.03429). Devices that - * claim to support this format must accept programs conforming to the - * following rules and requiring only its inherent execution-feature baseline - * plus features advertised for @ref QDMI_PROGRAM_FORMAT_QASM2. The rules - * are: - * - The program contains exactly one quantum register named `q`. - * - The number of qubits in the quantum register `q` matches the number of - * sites in the device. - * - The program only contains gate identifiers that are reported by the - * @ref QDMI_OPERATION_PROPERTY_NAME property of the device's operations. - * - * @par - * Given a program following these rules, the operations in the program - * are expected to be performed on the physical sites of the device as queried - * via @ref QDMI_DEVICE_PROPERTY_SITES. - * Specifically, an operation on `q[i]` is performed on the i-th site in the - * list of sites returned by the device. - * - * @note - * Devices may decide to support more general OpenQASM 2.0 programs that - * do not follow these rules, for example, using multiple qubit registers or - * arbitrary gates. However, in that case, no guarantees can be made about the - * mapping of qubits in the program to the physical sites of the device. - */ - QDMI_PROGRAM_FORMAT_QASM2 = 0, - /** - * @brief `char*` (string) An OpenQASM 3 program. - * @details A text-based representation of a quantum circuit in the - * [OpenQASM 3 language](https://openqasm.com/). Devices that claim to support - * this format must accept programs conforming to the same rules as for @ref - * QDMI_PROGRAM_FORMAT_QASM2 and requiring only its inherent - * execution-feature baseline plus features advertised for @ref - * QDMI_PROGRAM_FORMAT_QASM3. - * - * @par - * Besides the rules for OpenQASM 2.0 programs, OpenQASM 3 programs may - * be written using physical qubits, which are denoted by `$[NUM]`, with - * `[NUM]` being a non-negative integer denoting the physical qubit's index. - * If a program uses physical qubits, the operations in the program must be - * performed on the sites with indices corresponding to the physical qubits in - * the program. - * - * @note - * Devices may decide to support more general OpenQASM 3 programs that - * do not follow these rules, for example, using multiple qubit registers or - * arbitrary gates. However, in that case, no guarantees can be made about the - * mapping of qubits in the program to the physical sites of the device. - */ - QDMI_PROGRAM_FORMAT_QASM3 = 1, - /** - * @brief `char*` (string) A text-based QIR program complying to the QIR base - * profile. - * @details A text-based representation of a quantum circuit in the Quantum - * Intermediate Representation (QIR) format; specifically, the [QIR base - * profile](https://github.com/qir-alliance/qir-spec/blob/8b3fd47b7b70122a104e24733ef9de911576f7d6/specification/under_development/profiles/Base_Profile.md). - * Devices that claim to support this format must accept programs that follow - * the rules for the QIR base profile, require only its empty inherent - * execution-feature baseline plus features advertised for @ref - * QDMI_PROGRAM_FORMAT_QIRBASESTRING, and only contain operations that are - * reported by the @ref QDMI_OPERATION_PROPERTY_NAME property of the device's - * operations (for example, `@__quantum__qis__[NAME]__body`, where `[NAME]` is - * the name of the operation). - * - * @par - * QIR has a similar distinction between dynamically allocated and static - * hardware qubits as @ref QDMI_PROGRAM_FORMAT_QASM3. The same rules apply for - * the mapping of qubits in the program to the physical sites of the device. - * Specifically, if the program only allocates a single register named `q` - * with as many qubits as there are sites in the device, the operations in the - * program are expected to be performed on the physical sites of the device as - * queried via @ref QDMI_DEVICE_PROPERTY_SITES. If the program uses static - * qubit addresses (for example, `ptr inttoptr (i64 1 to ptr)`), the - * operations in the program must be performed on the sites with indices - * corresponding to the static qubit addresses in the program. - * - * @note Devices may decide to support more general QIR programs that do not - * follow these rules, for example, using multiple qubit registers or - * arbitrary gates. However, in that case, no guarantees can be made about the - * mapping of qubits in the program to the physical sites of the device. - */ - QDMI_PROGRAM_FORMAT_QIRBASESTRING = 2, - /** - * @brief `void*` A QIR binary complying to the QIR base profile. - * @details A binary representation of a quantum circuit in the Quantum - * Intermediate Representation (QIR) format; specifically, the [QIR base - * profile](https://github.com/qir-alliance/qir-spec/blob/8b3fd47b7b70122a104e24733ef9de911576f7d6/specification/under_development/profiles/Base_Profile.md). - * Its inherent execution-feature baseline is empty, as for @ref - * QDMI_PROGRAM_FORMAT_QIRBASESTRING, while optional features are advertised - * independently for @ref QDMI_PROGRAM_FORMAT_QIRBASEMODULE. - * - * @see - * QDMI_PROGRAM_FORMAT_QIRBASESTRING for more information on the QIR base - * profile and the expected behavior of devices supporting this format. - */ - QDMI_PROGRAM_FORMAT_QIRBASEMODULE = 3, - /** - * @brief `char*` (string) A text-based QIR program complying to the QIR - * adaptive profile. - * @details A text-based representation of a quantum circuit in the Quantum - * Intermediate Representation (QIR) format; specifically, the [QIR adaptive - * profile](https://github.com/qir-alliance/qir-spec/blob/8b3fd47b7b70122a104e24733ef9de911576f7d6/specification/under_development/profiles/Adaptive_Profile.md). - * Its inherent execution-feature baseline consists of @ref - * QDMI_PROGRAM_FEATURE_MIDCIRCUITMEASUREMENT, @ref - * QDMI_PROGRAM_FEATURE_MEASUREDQUBITREUSE, @ref - * QDMI_PROGRAM_FEATURE_MEASUREMENTRESULTUSE, @ref - * QDMI_PROGRAM_FEATURE_BOOLEANCOMPUTATION, and @ref - * QDMI_PROGRAM_FEATURE_FORWARDBRANCHING. Devices must accept programs that - * follow the QIR adaptive profile, only contain reported operations, and - * require only that baseline plus features advertised for @ref - * QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING. - * - * @see QDMI_PROGRAM_FORMAT_QIRBASESTRING for more information on the QIR base - * profile and the expected behavior of devices supporting this format. - */ - QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING = 4, - /** - * @brief `void*` A QIR binary complying to the QIR adaptive profile. - * @details A binary representation of a quantum circuit in the Quantum - * Intermediate Representation (QIR) format; specifically, the [QIR adaptive - * profile](https://github.com/qir-alliance/qir-spec/blob/8b3fd47b7b70122a104e24733ef9de911576f7d6/specification/under_development/profiles/Adaptive_Profile.md). - * Its inherent execution-feature baseline is the same as for @ref - * QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, while optional features are - * advertised independently for @ref QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE. - * - * @see QDMI_PROGRAM_FORMAT_QIRBASESTRING for more information on the QIR base - * profile and the expected behavior of devices supporting this format. - */ - QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE = 5, - /** - * @brief `void*` A calibration program. - * @details This program format is used to request the device to perform a - * calibration run. Triggering a calibration run does not require a program to - * be set via @ref QDMI_DEVICE_JOB_PARAMETER_PROGRAM. - */ - QDMI_PROGRAM_FORMAT_CALIBRATION = 6, - /** - * @brief `void*` A QPY program. - * @details A binary representation of a Qiskit `QuantumCircuit` in the - * [QPY format](https://quantum.cloud.ibm.com/docs/en/api/qiskit/qpy). - * Devices must accept circuits satisfying the structural rules described for - * @ref QDMI_PROGRAM_FORMAT_QASM3 and requiring only QPY's empty inherent - * execution-feature baseline plus features advertised for @ref - * QDMI_PROGRAM_FORMAT_QPY. - * - * @see QDMI_PROGRAM_FORMAT_QASM3 for more information on the expected - * behavior of devices supporting this format. - */ - QDMI_PROGRAM_FORMAT_QPY = 7, - /** - * @brief `char*` (string) A program in the IQM data transfer format. - * @details A text-based, proprietary representation of a quantum circuit in - * the [IQM data transfer - * format](https://docs.meetiqm.com/iqm-client/api/iqm.iqm_client.models.html), - * encoded as a JSON string. - */ - QDMI_PROGRAM_FORMAT_IQMJSON = 8, - /** - * @brief `QDMI_Job*`/`QDMI_Device_Job*` (@ref QDMI_Job list / @ref - * QDMI_Device_Job list) A list of jobs within a batch job. - * @details This program format is used to submit a batch job, i.e., a job - * that consists of multiple sub-jobs. The program must be a list of jobs - * created via @ref QDMI_device_create_job or @ref - * QDMI_device_session_create_device_job. These jobs must be configured - * completely but not submitted. If a batch job contains already submitted - * jobs, @ref QDMI_job_submit or @ref QDMI_device_job_submit on the batch job - * will return @ref QDMI_ERROR_BADSTATE. - * @par - * Querying results from a batch job directly is not possible and will result - * in @ref QDMI_ERROR_NOTSUPPORTED Instead, the results must be queried from - * the individual jobs after they finished. If the device supports it, each - * job in the batch can be queried for its status or waited for. However, - * in any case, individual jobs in a batch cannot be canceled and this will - * result in @ref QDMI_ERROR_NOTSUPPORTED. - */ - QDMI_PROGRAM_FORMAT_BATCHJOB = 9, - /** - * @brief The maximum value of the enum. - * @details It can be used by devices for bounds checking and validation of - * function parameters. - * - * @attention This value must remain the last regular member of the enum - * besides the custom members and must be updated when new members are added. - */ - QDMI_PROGRAM_FORMAT_MAX = 10, - /** - * @brief This enum value is reserved for a custom program format. - * @details The device defines the meaning and the type of this value. - * @attention The value of this enum member must not be changed to maintain - * binary compatibility. - */ - QDMI_PROGRAM_FORMAT_CUSTOM1 = 999999995, - /// @see QDMI_PROGRAM_FORMAT_CUSTOM1 - QDMI_PROGRAM_FORMAT_CUSTOM2 = 999999996, - /// @see QDMI_PROGRAM_FORMAT_CUSTOM1 - QDMI_PROGRAM_FORMAT_CUSTOM3 = 999999997, - /// @see QDMI_PROGRAM_FORMAT_CUSTOM1 - QDMI_PROGRAM_FORMAT_CUSTOM4 = 999999998, - /// @see QDMI_PROGRAM_FORMAT_CUSTOM1 - QDMI_PROGRAM_FORMAT_CUSTOM5 = 999999999 +#define QDMI_MAKE_VERSION(major, minor, patch) \ + ((((uint32_t)(major) & 0x3FFU) << 22U) | \ + (((uint32_t)(minor) & 0x3FFU) << 12U) | ((uint32_t)(patch) & 0xFFFU)) + +/// Extract the Semantic Versioning major component of a packed version. +#define QDMI_VERSION_MAJOR(version) (((uint32_t)(version) >> 22U) & 0x3FFU) +/// Extract the Semantic Versioning minor component of a packed version. +#define QDMI_VERSION_MINOR(version) (((uint32_t)(version) >> 12U) & 0x3FFU) +/// Extract the Semantic Versioning patch component of a packed version. +#define QDMI_VERSION_PATCH(version) ((uint32_t)(version) & 0xFFFU) + +/** @brief Encoding of a submitted payload. */ +enum QDMI_PROGRAM_ENCODING_T { + QDMI_PROGRAM_ENCODING_TEXT = 1, ///< NUL-terminated text. + QDMI_PROGRAM_ENCODING_BINARY = 2 ///< Arbitrary bytes. }; -/// Program format type. -typedef enum QDMI_PROGRAM_FORMAT_T QDMI_Program_Format; +/// Program encoding type. +typedef enum QDMI_PROGRAM_ENCODING_T QDMI_Program_Encoding; /** - * @brief Enum of atomic execution features that a device can support for a - * program format. - * @details These features describe runtime semantics independently of the - * program syntax. A program may require several features, and a device - * advertises each optional feature separately for the applicable program - * format through @ref QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES. Features - * guaranteed by a standardized format remain implicit and need not be - * repeated. Support for one optional feature does not imply support for any - * other feature. + * @brief Exact program format accepted by a device. + * @details All fields take part in identity. `version` is the nonzero, exact + * packed major, minor, and patch release of the payload specification. Devices + * list every accepted descriptor separately. Clients must not infer + * compatibility between versions, profiles, or encodings. + * + * The `id` and `profile` arrays must be NUL-terminated, and every byte after + * the first NUL must be zero. IDs are case-sensitive. QDMI reserves + * unqualified IDs for standard formats. The standard IDs are `openqasm` and + * `qir`. Vendor formats must use a namespaced ID such as `com.vendor.format`. + * QDMI does not define vendor-format versions, profiles, wire formats, or + * result semantics. Providers must document each vendor descriptor and its + * payload and result contract. An empty profile identifies a format without a + * named profile. QIR uses `base` and `adaptive`; OpenQASM uses an empty + * profile. + * + * In the following mappings, the regular-site list is the subsequence of @ref + * QDMI_DEVICE_PROPERTY_SITES for which @ref QDMI_SITE_PROPERTY_ISZONE is false, + * in provider order. Zone sites are not program-addressable qubits. Standard + * descriptors require the regular-site list size to equal @ref + * QDMI_DEVICE_PROPERTY_QUBITSNUM. Standard mappings cover local operations + * only; a vendor extension must define how a payload selects a zoned operation. + * + * Standard descriptors have the following portable mappings: + * - OpenQASM descriptors use text encoding and an empty profile. A program has + * exactly one quantum register named `q`, with one qubit for each regular + * site. `q[i]` maps to the i-th regular site. OpenQASM 3 physical qubit `$i` + * maps to the regular site whose @ref QDMI_SITE_PROPERTY_INDEX is `i`. + * Except for format-mandated measurement and output primitives, each quantum + * instruction name, number of quantum operands, and number of parameters must + * equal @ref QDMI_OPERATION_PROPERTY_NAME, + * @ref QDMI_OPERATION_PROPERTY_QUBITSNUM, and + * @ref QDMI_OPERATION_PROPERTY_PARAMETERSNUM for one reported local + * operation. + * - QIR descriptors use LLVM assembly for text encoding and LLVM bitcode for + * binary encoding. They use the `base` or `adaptive` profile. Descriptor + * version `N.M.P` identifies the QIR specification release and is independent + * of the output-schema version in @ref QDMI_JOB_RESULT_PROGRAMOUTPUT. Except + * for profile-mandated measurement, output, and runtime functions, a QIS + * function named `__quantum__qis__NAME__body` maps to the reported local + * operation whose name is `NAME`; its qubit and parameter operands must match + * that operation's qubit and parameter counts. A statically identified qubit + * with integer value `i` maps to the regular site whose @ref + * QDMI_SITE_PROPERTY_INDEX is `i`. A device that advertises QIR must assign + * the regular-site index set to `[0, N)`, where `N` is @ref + * QDMI_DEVICE_PROPERTY_QUBITSNUM. Dynamic qubit allocation has no portable + * site mapping. + * A provider may accept a wider format subset, but clients must not rely on a + * portable mapping outside these rules. + * Advertising a standard descriptor guarantees only its normative baseline and + * its reported optional program features, not every construct expressible in + * the format. + * + * The QIR Adaptive baseline includes mid-circuit measurement, measured-qubit + * reuse, measurement-result use, Boolean computation, and forward branching. + * Devices report optional QIR module flags as program features. Other standard + * descriptors have an empty program-feature baseline. */ -enum QDMI_PROGRAM_FEATURE_T { - /** - * @brief No execution feature. - * @details This value is a metadata marker, not an execution feature. It - * allows a @ref QDMI_Program_Format_Feature record to carry completeness - * information when no supported feature is listed. - */ - QDMI_PROGRAM_FEATURE_NONE = 0, - /** - * @brief Measurement followed by further quantum execution or adaptive use - * of the result. - */ - QDMI_PROGRAM_FEATURE_MIDCIRCUITMEASUREMENT = 1, - /// Continued use of a qubit after it has been measured. - QDMI_PROGRAM_FEATURE_MEASUREDQUBITREUSE = 2, - /** - * @brief Runtime use of a measurement result beyond terminal reporting or - * return. - */ - QDMI_PROGRAM_FEATURE_MEASUREMENTRESULTUSE = 3, - /// Runtime Boolean computation. - QDMI_PROGRAM_FEATURE_BOOLEANCOMPUTATION = 4, - /// Runtime integer computation. - QDMI_PROGRAM_FEATURE_INTEGERCOMPUTATION = 5, - /// Runtime floating-point computation. - QDMI_PROGRAM_FEATURE_FLOATCOMPUTATION = 6, - /// Runtime conditional forward branching. - QDMI_PROGRAM_FEATURE_FORWARDBRANCHING = 7, - /// Runtime counted iteration. - QDMI_PROGRAM_FEATURE_COUNTEDITERATION = 8, - /// Runtime condition-terminated looping. - QDMI_PROGRAM_FEATURE_CONDITIONALLOOP = 9, - /// Runtime multiway branching. - QDMI_PROGRAM_FEATURE_MULTIWAYBRANCHING = 10, - /// Runtime definitions of and calls to IR-defined functions. - QDMI_PROGRAM_FEATURE_IRDEFINEDFUNCTIONS = 11, - /// Multiple return points in an entry point or IR-defined function. - QDMI_PROGRAM_FEATURE_MULTIPLERETURNPOINTS = 12, - /** - * @brief The maximum value of the enum. - * @details It can be used by devices for bounds checking and validation of - * feature records. - * - * @attention This value must remain the last regular member of the enum - * besides the custom members and must be updated when new members are added. - */ - QDMI_PROGRAM_FEATURE_MAX = 13, - /** - * @brief This enum value is reserved for a custom program feature. - * @details The device defines the meaning of this feature. - * @attention The value of this enum member must not be changed to maintain - * binary compatibility. - */ - QDMI_PROGRAM_FEATURE_CUSTOM1 = 999999995, - /// @see QDMI_PROGRAM_FEATURE_CUSTOM1 - QDMI_PROGRAM_FEATURE_CUSTOM2 = 999999996, - /// @see QDMI_PROGRAM_FEATURE_CUSTOM1 - QDMI_PROGRAM_FEATURE_CUSTOM3 = 999999997, - /// @see QDMI_PROGRAM_FEATURE_CUSTOM1 - QDMI_PROGRAM_FEATURE_CUSTOM4 = 999999998, - /// @see QDMI_PROGRAM_FEATURE_CUSTOM1 - QDMI_PROGRAM_FEATURE_CUSTOM5 = 999999999 -}; +typedef struct QDMI_PROGRAM_FORMAT_T { + uint32_t version; ///< Exact version packed with @ref QDMI_MAKE_VERSION. + uint32_t encoding; ///< One @ref QDMI_Program_Encoding value. + char id[QDMI_PROGRAM_ID_SIZE]; ///< NUL-terminated format ID. + char profile[QDMI_PROGRAM_ID_SIZE]; ///< NUL-terminated profile ID. +} QDMI_Program_Format; + +/// Maximum bytes, including the terminating NUL, in a feature ID. +#define QDMI_PROGRAM_FEATURE_ID_SIZE 64U + +/// Maximum bytes, including the terminating NUL, in a constraint ID. +#define QDMI_PROGRAM_CONSTRAINT_ID_SIZE 64U + +/** + * @brief One optional feature guarantee for an exact program format. + * @details `id` and `constraint_id` must be NUL-terminated, and every byte + * after the first NUL must be zero. QDMI reserves unqualified IDs for standard + * features and constraints. Vendor-defined IDs must be namespaced. + * + * Records with the same `id` and `value` describe one feature group. An empty + * `constraint_id` means that the group is unrestricted and + * `constraint_value` must be zero. An unrestricted group contains exactly one + * record. Otherwise, every record in the group is one constraint and all + * constraints are conjunctive. A constrained group must not repeat a + * constraint ID. Different values for the same feature ID are alternatives, + * such as supported integer widths. + * + * A client that does not understand a feature ignores it. A client that + * understands a feature must treat a group as unusable if it contains an + * unknown constraint or if any record violates a representation or grouping + * rule above. A group is also unusable if it applies a known constraint to a + * feature for which that constraint is not defined, or if a constraint + * documented as positive has value zero. Future QDMI revisions can add new + * typed constraints without weakening this fail-closed rule. + */ +typedef struct QDMI_PROGRAM_FEATURE_T { + char id[QDMI_PROGRAM_FEATURE_ID_SIZE]; ///< NUL-terminated feature ID. + uint64_t value; ///< Feature-specific value. + char constraint_id[QDMI_PROGRAM_CONSTRAINT_ID_SIZE]; + ///< NUL-terminated constraint ID, or empty for no constraint. + uint64_t constraint_value; ///< Constraint-specific value. +} QDMI_Program_Feature; + +/** + * @brief Initialize an unrestricted program feature for C or C++. + * @param feature_id String-literal feature ID. + * @param feature_value Feature-specific value. + */ +#define QDMI_PROGRAM_FEATURE_UNCONSTRAINED(feature_id, feature_value) \ + {feature_id, feature_value, "", 0U} -/// Program feature type. -typedef enum QDMI_PROGRAM_FEATURE_T QDMI_Program_Feature; +/** + * @brief Measure a qubit before the end of one execution. + * @details This Boolean feature uses value zero. It does not by itself + * guarantee same-execution use of the result or reuse of the measured qubit. + */ +#define QDMI_PROGRAM_FEATURE_MID_CIRCUIT_MEASUREMENT "mid-circuit-measurement" +/** + * @brief Apply a quantum operation to a measured qubit in the same execution. + * @details This Boolean feature uses value zero. + */ +#define QDMI_PROGRAM_FEATURE_MEASURED_QUBIT_REUSE "measured-qubit-reuse" +/** + * @brief Read a measurement result during the same execution. + * @details This Boolean feature uses value zero. Control flow based on the + * result also requires the relevant branching or loop feature. + */ +#define QDMI_PROGRAM_FEATURE_MEASUREMENT_RESULT_USE "measurement-result-use" +/** + * @brief Compute Boolean values during execution. + * @details This Boolean feature uses value zero. It includes Boolean logic and + * comparisons whose operand types are otherwise supported by the descriptor. + */ +#define QDMI_PROGRAM_FEATURE_BOOLEAN_COMPUTATION "boolean-computation" +/** + * @brief Select a later program region from a value computed during execution. + * @details This Boolean feature uses value zero and does not include a backward + * branch. + */ +#define QDMI_PROGRAM_FEATURE_FORWARD_BRANCHING "forward-branching" +/** + * @brief Repeat a program region a known finite number of times. + * @details This Boolean feature uses value zero. The trip count does not depend + * on a value produced by the repeated region. + */ +#define QDMI_PROGRAM_FEATURE_COUNTED_ITERATION "counted-iteration" +/** + * @brief Repeat a program region based on a value computed during execution. + * @details This Boolean feature uses value zero and includes a backward branch. + */ +#define QDMI_PROGRAM_FEATURE_CONDITIONAL_LOOP "conditional-loop" +/** + * @brief Select one of more than two program regions during execution. + * @details This Boolean feature uses value zero. The selector type requires its + * corresponding computation feature. + */ +#define QDMI_PROGRAM_FEATURE_MULTIWAY_BRANCHING "multiway-branching" +/** + * @brief Compute integer values during execution. + * @details The feature value is one exact supported integer width in bits. + * Devices return one feature group for each supported width. + */ +#define QDMI_PROGRAM_FEATURE_INTEGER_COMPUTATION "integer-computation" +/** + * @brief Compute floating-point values during execution. + * @details The feature value is one exact supported floating-point width in + * bits. Devices return one feature group for each supported width. + */ +#define QDMI_PROGRAM_FEATURE_FLOAT_COMPUTATION "float-computation" +/** + * @brief Define and call functions beyond the entry point and format-mandated + * declarations or runtime calls. + * @details This Boolean feature uses value zero. + */ +#define QDMI_PROGRAM_FEATURE_IR_FUNCTIONS "ir-functions" +/** + * @brief Use more than one return point in one function or entry point. + * @details This Boolean feature uses value zero. + */ +#define QDMI_PROGRAM_FEATURE_MULTIPLE_RETURN_POINTS "multiple-return-points" +/** + * @brief Allocate and release qubits during execution. + * @details This Boolean feature uses value zero. + */ +#define QDMI_PROGRAM_FEATURE_DYNAMIC_QUBIT_MANAGEMENT "dynamic-qubit-management" +/** + * @brief Allocate and release result handles during execution. + * @details This Boolean feature uses value zero. + */ +#define QDMI_PROGRAM_FEATURE_DYNAMIC_RESULT_MANAGEMENT \ + "dynamic-result-management" +/** + * @brief Create and access arrays during execution. + * @details This Boolean feature uses value zero. Element types must be + * supported by the descriptor or another reported computation feature. + */ +#define QDMI_PROGRAM_FEATURE_ARRAYS "arrays" /** - * @brief One atomic execution-feature record for a program format. - * @details All records for the same format must use the same value for @ref - * optional_features_complete. Every listed feature is known to be supported. - * A zero value means that additional optional features may also be supported. - * A non-zero value means that the records describe every supported feature not - * already guaranteed by the program format. - * @par - * When no feature is listed, a device may return one record with @ref feature - * set to @ref QDMI_PROGRAM_FEATURE_NONE. Together with @ref - * optional_features_complete, this distinguishes unknown optional-feature - * metadata from a known-empty optional-feature set. @ref - * QDMI_PROGRAM_FEATURE_NONE must not be combined with another record for the - * same format. + * @brief Limit lexical control-flow nesting depth. + * @details The positive constraint value is the maximum number of enclosing + * branch and loop constructs, counting the outermost construct as depth one. + * This constraint applies to branching and iteration features. */ -typedef struct QDMI_PROGRAM_FORMAT_FEATURE_T { - /// Program format to which the feature metadata applies. - QDMI_Program_Format format; - /// Atomic execution feature, or @ref QDMI_PROGRAM_FEATURE_NONE. - QDMI_Program_Feature feature; - /// Whether all optional features for @ref format have been reported. - int optional_features_complete; -} QDMI_Program_Format_Feature; +#define QDMI_PROGRAM_CONSTRAINT_MAX_CONTROL_FLOW_NESTING_DEPTH \ + "max-control-flow-nesting-depth" +/** + * @brief Limit the trip count of each loop. + * @details The positive constraint value is the inclusive maximum number of + * times one loop body may execute. A compiler must prove this upper bound. This + * constraint applies to counted iteration and conditional loops. + */ +#define QDMI_PROGRAM_CONSTRAINT_MAX_ITERATION_COUNT "max-iteration-count" +/** + * @brief Limit the number of explicit cases in each multiway branch. + * @details The positive constraint value counts explicit cases and excludes a + * default case. This constraint applies to multiway branching. + */ +#define QDMI_PROGRAM_CONSTRAINT_MAX_CASE_COUNT "max-case-count" /** * @brief Enum of the formats the results can be returned in. @@ -1261,8 +1155,26 @@ typedef struct QDMI_PROGRAM_FORMAT_FEATURE_T { enum QDMI_JOB_RESULT_T { /** * @brief `char*` (string) The results of the individual shots as a - * comma-separated list, for example, "0010,1101,0101,1100,1001,1100" for four - * qubits and six shots. + * comma-separated list. + * @details Each bit string contains every flat bit output declared by the + * submitted payload. The first output in the order below is the leftmost bit: + * - OpenQASM 2 uses `creg` declarations in source order and increasing bit + * index within each declaration. + * - OpenQASM 3 uses bit-valued output declarations in source order and + * increasing bit index within each declaration. If the program has no + * explicit output declaration, the OpenQASM 3 implicit-output rules select + * the outputs before this ordering is applied. + * - QIR uses primitive result-recording calls in execution order. A result + * array contributes its elements in memory order. Container recording + * calls do not add bits. + * If the payload output cannot be represented losslessly as one fixed-width + * bit string per shot, queries for shots and histogram results return @ref + * QDMI_ERROR_NOTSUPPORTED. Clients can query @ref + * QDMI_JOB_RESULT_PROGRAMOUTPUT when the submitted descriptor defines a + * native output representation. + * + * The width is independent of the number of device sites. For example, + * "0010,1101,0101" represents three shots of four declared bit outputs. */ QDMI_JOB_RESULT_SHOTS = 0, /** @@ -1270,7 +1182,8 @@ enum QDMI_JOB_RESULT_T { * @details The histogram of the measurement results is represented as a * key-value mapping. This mapping is returned as a list of keys and an * equal-length list of values. The corresponding partners of keys and values - * can be found at the same index in the lists. + * can be found at the same index in the lists. Each key uses the same + * payload-declared logical bit order as @ref QDMI_JOB_RESULT_SHOTS. * * This constant denotes the list of keys, @ref QDMI_JOB_RESULT_HIST_VALUES * denotes the list of values. @@ -1334,6 +1247,17 @@ enum QDMI_JOB_RESULT_T { * QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS */ QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES = 8, + /** + * @brief `void*` The complete format-native program output. + * @details The exact descriptor defines the byte representation. The output + * is an arbitrary byte sequence and need not be NUL-terminated. For a QIR + * specification that defines an output schema, the sequence is a complete + * output-schema stream, including its required headers and every shot record. + * Devices that accept such a QIR descriptor must support this result. A + * device may return @ref QDMI_ERROR_NOTSUPPORTED when the submitted + * descriptor does not define a native output representation. + */ + QDMI_JOB_RESULT_PROGRAMOUTPUT = 9, /** * @brief The maximum value of the enum. * @details It can be used by devices for bounds checking and validation of @@ -1342,7 +1266,7 @@ enum QDMI_JOB_RESULT_T { * @attention This value must remain the last regular member of the enum * besides the custom members and must be updated when new members are added. */ - QDMI_JOB_RESULT_MAX = 9, + QDMI_JOB_RESULT_MAX = 10, /** * @brief This enum value is reserved for a custom result. * @details The device defines the meaning and the type of this result. diff --git a/include/qdmi/device.h b/include/qdmi/device.h index 06007f40..827847b3 100644 --- a/include/qdmi/device.h +++ b/include/qdmi/device.h @@ -285,6 +285,31 @@ QDMI_EXPORT int QDMI_device_session_query_device_property( QDMI_Device_Session session, QDMI_Device_Property prop, size_t size, void *value, size_t *size_ret); +/** + * @brief Query the complete optional feature guarantees for one exact program + * format. + * @param[in] session The initialized session used for the query. Must not be + * @c NULL. + * @param[in] format A descriptor returned by @ref + * QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS. Must not be @c NULL. + * @param[in] size The size of @p value in bytes. Ignored when @p value is + * @c NULL. + * @param[out] value Storage for a list of @ref QDMI_Program_Feature records, + * including all constraints, or @c NULL to query the required size. + * @param[out] size_ret The required list size in bytes, or @c NULL. + * @return @ref QDMI_SUCCESS when the complete optional set is available. A + * zero-byte result means that the format supports only its normative baseline. + * @return @ref QDMI_ERROR_NOTSUPPORTED when optional feature metadata is + * unknown for @p format. + * @return @ref QDMI_ERROR_INVALIDARGUMENT for a null handle or descriptor, an + * invalid descriptor, or an insufficient output buffer. + * @return @ref QDMI_ERROR_BADSTATE when @p session is not initialized. + * @return @ref QDMI_ERROR_FATAL if an unexpected error occurred. + */ +QDMI_EXPORT int QDMI_device_session_query_program_features( + QDMI_Device_Session session, const QDMI_Program_Format *format, size_t size, + QDMI_Program_Feature *value, size_t *size_ret); + /** * @brief Query a site property. * @param[in] session The session used for the query. Must not be @c NULL. diff --git a/templates/device/src/my_device.cpp b/templates/device/src/my_device.cpp index 3994b04e..bc33e0f8 100644 --- a/templates/device/src/my_device.cpp +++ b/templates/device/src/my_device.cpp @@ -141,6 +141,12 @@ int MY_QDMI_device_session_query_device_property( return QDMI_ERROR_NOTIMPLEMENTED; } +int MY_QDMI_device_session_query_program_features( + MY_QDMI_Device_Session session, const QDMI_Program_Format *format, + size_t size, QDMI_Program_Feature *value, size_t *size_ret) { + return QDMI_ERROR_NOTIMPLEMENTED; +} + int MY_QDMI_device_session_query_site_property(MY_QDMI_Device_Session session, MY_QDMI_Site site, const QDMI_Site_Property prop, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1d73881e..370d6983 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,7 +29,7 @@ add_subdirectory(utils) # ------------------------------------------------------------------------------ # create an executable in which the tests will be stored -add_executable(qdmi_test test_qdmi.cpp) +add_executable(qdmi_test test_program_feature.c test_qdmi.cpp) # link the Google test infrastructure to the test executable. target_link_libraries( diff --git a/test/test_program_feature.c b/test/test_program_feature.c new file mode 100644 index 00000000..962d11cf --- /dev/null +++ b/test/test_program_feature.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 - 2026 QDMI Maintainers + * All rights reserved. + * + * Licensed under the Apache License v2.0 with LLVM Exceptions (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "qdmi/constants.h" + +/// C uses void for a no-argument prototype. The C++ test calls this function. +// NOLINTNEXTLINE(misc-use-internal-linkage, modernize-redundant-void-arg) +QDMI_Program_Feature QDMI_test_unconstrained_program_feature(void) { + const QDMI_Program_Feature feature = QDMI_PROGRAM_FEATURE_UNCONSTRAINED( + QDMI_PROGRAM_FEATURE_INTEGER_COMPUTATION, 64U); + return feature; +} diff --git a/test/test_qdmi.cpp b/test/test_qdmi.cpp index e74218bf..39af8d42 100644 --- a/test/test_qdmi.cpp +++ b/test/test_qdmi.cpp @@ -38,15 +38,75 @@ #include extern "C" { #include +QDMI_Program_Feature QDMI_test_unconstrained_program_feature(void); } #include +#include #include -#include #include #include #include namespace { +static_assert(sizeof(QDMI_Program_Format) == 136U); +static_assert(sizeof(QDMI_Program_Feature) == 144U); +static_assert(QDMI_VERSION_MAJOR(QDMI_MAKE_VERSION(2, 1, 3)) == 2U); +static_assert(QDMI_VERSION_MINOR(QDMI_MAKE_VERSION(2, 1, 3)) == 1U); +static_assert(QDMI_VERSION_PATCH(QDMI_MAKE_VERSION(2, 1, 3)) == 3U); + +constexpr QDMI_Program_Feature CPP_UNCONSTRAINED_FEATURE = + QDMI_PROGRAM_FEATURE_UNCONSTRAINED( + QDMI_PROGRAM_FEATURE_MID_CIRCUIT_MEASUREMENT, 0); +static_assert(CPP_UNCONSTRAINED_FEATURE.value == 0U); +static_assert(CPP_UNCONSTRAINED_FEATURE.constraint_id[0] == '\0'); +static_assert(CPP_UNCONSTRAINED_FEATURE.constraint_value == 0U); + +constexpr std::string_view FLAT_SHOT_OUTPUT{"01"}; +constexpr std::string_view EXPECTED_QIR_PROGRAM_OUTPUT = + "HEADER\tschema_id\tordered\n" + "HEADER\tschema_version\t2.1\n" + "START\n" + "METADATA\tentry_point\n" + "METADATA\tqir_profiles\tbase_profile\n" + "METADATA\toutput_labeling_schema\tschema_id\n" + "METADATA\trequired_num_qubits\t2\n" + "METADATA\trequired_num_results\t2\n" + "OUTPUT\tRESULT\t0\n" + "OUTPUT\tRESULT\t1\n" + "END\t0\n" + "START\n" + "METADATA\tentry_point\n" + "METADATA\tqir_profiles\tbase_profile\n" + "METADATA\toutput_labeling_schema\tschema_id\n" + "METADATA\trequired_num_qubits\t2\n" + "METADATA\trequired_num_results\t2\n" + "OUTPUT\tRESULT\t0\n" + "OUTPUT\tRESULT\t1\n" + "END\t0\n"; + +constexpr QDMI_Program_Format QASM2_FORMAT{ + .version = QDMI_MAKE_VERSION(2, 0, 0), + .encoding = QDMI_PROGRAM_ENCODING_TEXT, + .id = "openqasm", + .profile = ""}; +constexpr QDMI_Program_Format QIR_BASE_TEXT_FORMAT{ + .version = QDMI_MAKE_VERSION(2, 1, 0), + .encoding = QDMI_PROGRAM_ENCODING_TEXT, + .id = "qir", + .profile = "base"}; +constexpr QDMI_Program_Format QIR_BASE_BINARY_FORMAT{ + .version = QDMI_MAKE_VERSION(2, 1, 0), + .encoding = QDMI_PROGRAM_ENCODING_BINARY, + .id = "qir", + .profile = "base"}; + +bool Same_format(const QDMI_Program_Format &lhs, + const QDMI_Program_Format &rhs) { + return lhs.version == rhs.version && lhs.encoding == rhs.encoding && + std::ranges::equal(lhs.id, rhs.id) && + std::ranges::equal(lhs.profile, rhs.profile); +} + /// Hash function for a pair struct Pair_hash { template @@ -57,6 +117,14 @@ struct Pair_hash { }; } // namespace +TEST(QDMIConstantsTest, UnconstrainedProgramFeatureMacroIsCCompatible) { + const auto feature = QDMI_test_unconstrained_program_feature(); + EXPECT_STREQ(feature.id, QDMI_PROGRAM_FEATURE_INTEGER_COMPUTATION); + EXPECT_EQ(feature.value, 64U); + EXPECT_STREQ(feature.constraint_id, ""); + EXPECT_EQ(feature.constraint_value, 0U); +} + // Instantiate the test suite with different parameters INSTANTIATE_TEST_SUITE_P( QDMIDevice, @@ -407,42 +475,66 @@ TEST_P(QDMIImplementationTest, QueryDeviceProperties) { QDMI_SUCCESS); EXPECT_GE(scale_factor, 0.0); - // Query the format-scoped execution features. The example device reports an - // incomplete pair of OpenQASM 2 features, known-empty optional profiles for - // QIR Base, and no calibration metadata. + // Query exact format-scoped execution features. size = 0; - ASSERT_EQ(QDMI_device_query_device_property( - device, QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES, 0, nullptr, - &size), + ASSERT_EQ(QDMI_device_query_program_features(device, &QASM2_FORMAT, 0, + nullptr, &size), QDMI_SUCCESS); - std::vector format_features( - size / sizeof(QDMI_Program_Format_Feature)); - ASSERT_EQ(QDMI_device_query_device_property( - device, QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES, size, - format_features.data(), nullptr), + std::vector format_features( + size / sizeof(QDMI_Program_Feature)); + ASSERT_EQ(QDMI_device_query_program_features(device, &QASM2_FORMAT, size, + format_features.data(), nullptr), QDMI_SUCCESS); - ASSERT_EQ(format_features.size(), 4); - EXPECT_EQ(format_features[0].format, QDMI_PROGRAM_FORMAT_QASM2); - EXPECT_EQ(format_features[0].feature, - QDMI_PROGRAM_FEATURE_MIDCIRCUITMEASUREMENT); - EXPECT_EQ(format_features[0].optional_features_complete, 0); - EXPECT_EQ(format_features[1].format, QDMI_PROGRAM_FORMAT_QASM2); - EXPECT_EQ(format_features[1].feature, - QDMI_PROGRAM_FEATURE_MEASUREDQUBITREUSE); - EXPECT_EQ(format_features[1].optional_features_complete, 0); - EXPECT_EQ(format_features[2].format, QDMI_PROGRAM_FORMAT_QIRBASESTRING); - EXPECT_EQ(format_features[3].format, QDMI_PROGRAM_FORMAT_QIRBASEMODULE); - EXPECT_EQ(format_features[2].feature, QDMI_PROGRAM_FEATURE_NONE); - EXPECT_NE(format_features[2].optional_features_complete, 0); - EXPECT_EQ(format_features[3].feature, QDMI_PROGRAM_FEATURE_NONE); - EXPECT_NE(format_features[3].optional_features_complete, 0); - EXPECT_TRUE(std::ranges::none_of(format_features, [](const auto &feature) { - return feature.format == QDMI_PROGRAM_FORMAT_CALIBRATION; - })); - EXPECT_EQ(QDMI_device_query_device_property( - device, QDMI_DEVICE_PROPERTY_PROGRAMFORMATFEATURES, size - 1, - format_features.data(), nullptr), + ASSERT_EQ(format_features.size(), 3); + EXPECT_STREQ(format_features.at(0).id, + QDMI_PROGRAM_FEATURE_MID_CIRCUIT_MEASUREMENT); + EXPECT_STREQ(format_features.at(0).constraint_id, ""); + EXPECT_EQ(format_features.at(0).constraint_value, 0U); + EXPECT_STREQ(format_features.at(1).id, + QDMI_PROGRAM_FEATURE_MEASURED_QUBIT_REUSE); + EXPECT_STREQ(format_features.at(2).id, + QDMI_PROGRAM_FEATURE_FORWARD_BRANCHING); + EXPECT_STREQ(format_features.at(2).constraint_id, + QDMI_PROGRAM_CONSTRAINT_MAX_CONTROL_FLOW_NESTING_DEPTH); + EXPECT_EQ(format_features.at(2).constraint_value, 1U); + EXPECT_EQ(QDMI_device_query_program_features(device, &QASM2_FORMAT, size - 1, + format_features.data(), nullptr), + QDMI_ERROR_INVALIDARGUMENT); + + size = 1; + EXPECT_EQ(QDMI_device_query_program_features(device, &QIR_BASE_TEXT_FORMAT, 0, + nullptr, &size), + QDMI_SUCCESS); + EXPECT_EQ(size, 0); + auto unsupported = QASM2_FORMAT; + unsupported.version = QDMI_MAKE_VERSION(3, 0, 0); + EXPECT_EQ(QDMI_device_query_program_features(device, &unsupported, 0, nullptr, + nullptr), + QDMI_ERROR_NOTSUPPORTED); + auto invalid_qir = QIR_BASE_TEXT_FORMAT; + std::ranges::fill(invalid_qir.profile, '\0'); + EXPECT_EQ(QDMI_device_query_program_features(device, &invalid_qir, 0, nullptr, + nullptr), + QDMI_ERROR_INVALIDARGUMENT); + constexpr QDMI_Program_Format invalid_qpy{ + .version = QDMI_MAKE_VERSION(1, 0, 0), + .encoding = QDMI_PROGRAM_ENCODING_BINARY, + .id = "qpy", + .profile = ""}; + EXPECT_EQ(QDMI_device_query_program_features(device, &invalid_qpy, 0, nullptr, + nullptr), QDMI_ERROR_INVALIDARGUMENT); + constexpr QDMI_Program_Format vendor_qpy{ + .version = QDMI_MAKE_VERSION(1, 0, 0), + .encoding = QDMI_PROGRAM_ENCODING_BINARY, + .id = "com.vendor.qpy", + .profile = ""}; + EXPECT_EQ(QDMI_device_query_program_features(device, &vendor_qpy, 0, nullptr, + nullptr), + QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ( + QDMI_device_query_program_features(device, nullptr, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); // The example device does not support neutral atom-specific properties EXPECT_EQ( @@ -492,14 +584,13 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { EXPECT_EQ( QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, 0, nullptr), QDMI_SUCCESS); - QDMI_Program_Format format = QDMI_PROGRAM_FORMAT_MAX; + QDMI_Program_Format format{}; EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, sizeof(QDMI_Program_Format), &format), QDMI_ERROR_INVALIDARGUMENT); - constexpr std::array supported_formats = { - QDMI_PROGRAM_FORMAT_QASM2, QDMI_PROGRAM_FORMAT_QIRBASESTRING, - QDMI_PROGRAM_FORMAT_QIRBASEMODULE, QDMI_PROGRAM_FORMAT_CALIBRATION}; + constexpr std::array supported_formats = {QASM2_FORMAT, QIR_BASE_TEXT_FORMAT, + QIR_BASE_BINARY_FORMAT}; for (const auto &supported_format : supported_formats) { ASSERT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, @@ -518,16 +609,14 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { } constexpr std::array unsupported_formats = { - QDMI_PROGRAM_FORMAT_QASM3, - QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, - QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE, - QDMI_PROGRAM_FORMAT_QPY, - QDMI_PROGRAM_FORMAT_IQMJSON, - QDMI_PROGRAM_FORMAT_CUSTOM1, - QDMI_PROGRAM_FORMAT_CUSTOM2, - QDMI_PROGRAM_FORMAT_CUSTOM3, - QDMI_PROGRAM_FORMAT_CUSTOM4, - QDMI_PROGRAM_FORMAT_CUSTOM5}; + QDMI_Program_Format{.version = QDMI_MAKE_VERSION(3, 0, 0), + .encoding = QDMI_PROGRAM_ENCODING_TEXT, + .id = "openqasm", + .profile = ""}, + QDMI_Program_Format{.version = QDMI_MAKE_VERSION(2, 1, 0), + .encoding = QDMI_PROGRAM_ENCODING_TEXT, + .id = "qir", + .profile = "adaptive"}}; for (const auto &unsupported_format : unsupported_formats) { EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, @@ -552,7 +641,7 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_CUSTOM5, 0, nullptr), QDMI_ERROR_NOTSUPPORTED); - format = QDMI_PROGRAM_FORMAT_QASM2; + format = QASM2_FORMAT; EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, sizeof(QDMI_Program_Format), &format), QDMI_SUCCESS); @@ -564,7 +653,7 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { &size), QDMI_SUCCESS); EXPECT_EQ(size, sizeof(QDMI_Program_Format)); - EXPECT_EQ(format, QDMI_PROGRAM_FORMAT_QASM2); + EXPECT_TRUE(Same_format(format, QASM2_FORMAT)); size_t shots = 5; EXPECT_EQ(QDMI_job_set_parameter(nullptr, QDMI_JOB_PARAMETER_SHOTSNUM, @@ -640,13 +729,13 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; creg c[2]; -h q[0]; +rx(pi/2) q[0]; cx q[0], q[1]; measure q -> c; )"; QDMI_Job job = nullptr; EXPECT_EQ(QDMI_device_create_job(dev, &job), QDMI_SUCCESS); - const auto format = QDMI_PROGRAM_FORMAT_QASM2; + const auto format = QASM2_FORMAT; EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, sizeof(QDMI_Program_Format), &format), QDMI_SUCCESS); @@ -691,13 +780,15 @@ TEST_P(QDMIImplementationTest, GetResultsCornerCases) { EXPECT_EQ( QDMI_job_get_results(job, QDMI_JOB_RESULT_CUSTOM5, 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_PROGRAMOUTPUT, 0, nullptr, + nullptr), + QDMI_ERROR_NOTSUPPORTED); } TEST_P(QDMIImplementationTest, GetShots) { if (mode == TEST_SESSION_MODE::READONLY) { GTEST_SKIP() << "Skipping test for read-only session"; } - const auto fomac = FoMaC(device); const size_t shots_num = 64; QDMI_Job job = Submit_test_job(device, shots_num); size_t size = 0; @@ -707,14 +798,14 @@ TEST_P(QDMIImplementationTest, GetShots) { ASSERT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_SHOTS, size, shots.data(), nullptr), QDMI_SUCCESS); - std::vector shots_vec; + size_t shots_seen = 0; std::string token; std::stringstream ss(shots); while (std::getline(ss, token, ',')) { - shots_vec.emplace_back(token); - ASSERT_EQ(token.size(), fomac.get_qubits_num()); + EXPECT_EQ(token, FLAT_SHOT_OUTPUT); + ++shots_seen; } - ASSERT_EQ(shots_vec.size(), shots_num); + EXPECT_EQ(shots_seen, shots_num); QDMI_job_free(job); } @@ -722,7 +813,6 @@ TEST_P(QDMIImplementationTest, GetHistogram) { if (mode == TEST_SESSION_MODE::READONLY) { GTEST_SKIP() << "Skipping test for read-only session"; } - const auto fomac = FoMaC(device); const size_t shots_num = 64; QDMI_Job job = Submit_test_job(device, shots_num); @@ -738,14 +828,9 @@ TEST_P(QDMIImplementationTest, GetHistogram) { std::string token; std::stringstream ss(key_list); while (std::getline(ss, token, ',')) { - ASSERT_EQ(token.size(), fomac.get_qubits_num()); key_vec.emplace_back(token); } - - // keys should be sorted - for (size_t i = 1; i < key_vec.size(); ++i) { - ASSERT_LT(key_vec[i - 1], key_vec[i]); - } + EXPECT_THAT(key_vec, testing::ElementsAre(FLAT_SHOT_OUTPUT)); size_t val_size = 0; ASSERT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_HIST_VALUES, 0, nullptr, @@ -758,18 +843,78 @@ TEST_P(QDMIImplementationTest, GetHistogram) { val_vec.data(), nullptr), QDMI_SUCCESS); - size_t sum = 0; - for (const auto &val : val_vec) { - sum += val; - } - ASSERT_EQ(sum, shots_num); + EXPECT_THAT(val_vec, testing::ElementsAre(shots_num)); - std::unordered_map results; - for (size_t i = 0; i < key_vec.size(); ++i) { - results[key_vec[i]] = val_vec[i]; + QDMI_job_free(job); +} + +TEST_P(QDMIImplementationTest, GetProgramOutput) { + if (mode == TEST_SESSION_MODE::READONLY) { + GTEST_SKIP() << "Skipping test for read-only session"; } - ASSERT_EQ(results.size(), key_vec.size()); + constexpr auto qir_program = std::to_array(R"( +@0 = internal constant [3 x i8] c"r0\00" +@1 = internal constant [3 x i8] c"r1\00" + +define i64 @main() #0 { +entry: + call void @__quantum__rt__initialize(ptr null) + br label %body +body: + br label %measurements +measurements: + call void @__quantum__qis__mz__body(ptr null, ptr writeonly null) + call void @__quantum__qis__mz__body(ptr inttoptr (i64 1 to ptr), ptr writeonly inttoptr (i64 1 to ptr)) + br label %output +output: + call void @__quantum__rt__result_record_output(ptr null, ptr @0) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 1 to ptr), ptr @1) + ret i64 0 +} +declare void @__quantum__rt__initialize(ptr) +declare void @__quantum__qis__mz__body(ptr, ptr writeonly) #1 +declare void @__quantum__rt__result_record_output(ptr, ptr) + +attributes #0 = { "entry_point" "qir_profiles"="base_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="2" "required_num_results"="2" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 1} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} +)"); + QDMI_Job job = nullptr; + ASSERT_EQ(QDMI_device_create_job(device, &job), QDMI_SUCCESS); + ASSERT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(QDMI_Program_Format), + &QIR_BASE_TEXT_FORMAT), + QDMI_SUCCESS); + constexpr size_t shots_num = 2U; + ASSERT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_SHOTSNUM, + sizeof(shots_num), &shots_num), + QDMI_SUCCESS); + ASSERT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAM, + sizeof(qir_program), qir_program.data()), + QDMI_SUCCESS); + ASSERT_EQ(QDMI_job_submit(job), QDMI_SUCCESS); + ASSERT_EQ(QDMI_job_wait(job, 0), QDMI_SUCCESS); + + size_t size = 0; + ASSERT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_PROGRAMOUTPUT, 0, nullptr, + &size), + QDMI_SUCCESS); + ASSERT_EQ(size, EXPECTED_QIR_PROGRAM_OUTPUT.size()); + std::vector output(size); + EXPECT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_PROGRAMOUTPUT, size - 1, + output.data(), nullptr), + QDMI_ERROR_INVALIDARGUMENT); + ASSERT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_PROGRAMOUTPUT, size, + output.data(), nullptr), + QDMI_SUCCESS); + EXPECT_EQ(std::string_view(output.data(), output.size()), + EXPECTED_QIR_PROGRAM_OUTPUT); QDMI_job_free(job); } @@ -1179,26 +1324,13 @@ TEST_P(QDMIImplementationTest, SessionQuerySessionProperty) { QDMI_SUCCESS); } -TEST_P(QDMIImplementationTest, SupportsCalibration) { - if (mode == TEST_SESSION_MODE::READONLY) { - GTEST_SKIP() << "Skipping test for read-only session"; - } - QDMI_Job job = nullptr; - QDMI_Program_Format format = QDMI_PROGRAM_FORMAT_CALIBRATION; - QDMI_device_create_job(device, &job); - const auto ret = QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, - sizeof(QDMI_Program_Format), &format); - EXPECT_EQ(ret, QDMI_SUCCESS); - EXPECT_EQ(QDMI_job_submit(job), QDMI_SUCCESS); -} - TEST_P(QDMIImplementationTest, NeedsCalibration) { - size_t needs_calibration = 0; - const auto ret = QDMI_device_query_device_property( - device, QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION, sizeof(size_t), - &needs_calibration, nullptr); - EXPECT_EQ(ret, QDMI_SUCCESS); - EXPECT_EQ(needs_calibration, 0); + size_t needs_calibration = 1; + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION, sizeof(size_t), + &needs_calibration, nullptr), + QDMI_SUCCESS); + EXPECT_EQ(needs_calibration, 0U); } TEST_P(QDMIImplementationTest, QueryPulseSupportLevel) { From 92464191be344f53653456bb96bc6a7f98b00c40 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 24 Aug 2026 01:00:00 +0000 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=92=A5=20Complete=20exact=20payload?= =?UTF-8?q?=20contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define canonical program-format value semantics, exact text and binary framing, logical output order, custom enum ranges, and translation and retrieval ownership. Assisted-by: GPT-5.6 Sol via Codex --- CHANGELOG.md | 14 ++- UPGRADING.md | 45 +++++-- examples/device/src/cxx_device.cpp | 72 ++++------- examples/driver/qdmi_example_driver.cpp | 12 +- include/qdmi/client.h | 32 +++-- include/qdmi/constants.h | 121 +++++++++++++++---- include/qdmi/device.h | 11 +- test/test_qdmi.cpp | 153 ++++++++++++++++++++++-- 8 files changed, 339 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d83ec66b..73c0d8b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,14 +14,18 @@ clients compiled against a different minor or major version. ### Added -- ✨ Add descriptor-scoped execution-feature queries and a format-defined - program-output result ([#508]) ([\@simon1hofmann], [\@burgholzer]). +- ✨ Add descriptor-scoped execution-feature queries, exact descriptor equality, + and a format-defined program-output result ([#508]) ([\@simon1hofmann], + [\@burgholzer]). ### Changed -- 💥 Replace program-format enums with exact format descriptors and define the - flat-bit order of shot and histogram results ([#508]) ([\@simon1hofmann], - [\@burgholzer]). +- 💥 Replace program-format enums with exact format descriptors, validate text + and binary payload framing, and place logical output slot zero at the right of + shot and histogram strings ([#508]) ([\@simon1hofmann], [\@burgholzer]). +- 💥 Treat every enum value from `999999995` through `INT32_MAX` as a valid + provider-defined value while keeping the intervening gap invalid ([#508]) + ([\@simon1hofmann], [\@burgholzer]). - 💥 Require device libraries to export `QDMI_device_session_retrieve_device_job_by_id`, while allowing the function to return `QDMI_ERROR_NOTSUPPORTED` ([#508]) ([\@simon1hofmann], diff --git a/UPGRADING.md b/UPGRADING.md index 1fd3343d..08f2e4d2 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -13,18 +13,21 @@ QDMI replaces the program-format enum with an exact `QDMI_Program_Format` descriptor. The descriptor contains an ID, packed Semantic Versioning release, profile, and text or binary encoding. Devices list every accepted descriptor in `QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS`. Clients must submit one of those -exact values and must not infer version compatibility. +exact values and must not infer version compatibility. Descriptor identity is a +value contract: callers can reconstruct a canonical value and compare it with +`QDMI_program_format_equal`. ```c const QDMI_Program_Format qasm3 = { QDMI_MAKE_VERSION(3, 0, 0), QDMI_PROGRAM_ENCODING_TEXT, "openqasm", ""}; ``` -The size of a text payload includes its terminating NUL. Binary payloads remain -arbitrary byte sequences. +Text payloads contain exactly one trailing NUL and no earlier NUL; their size +includes that NUL. Binary payloads are nonempty arbitrary byte sequences. QDMI reserves the unqualified IDs `openqasm` and `qir` for standard formats. -Vendor formats use namespaced IDs such as `com.vendor.format`. +Vendor formats use `.` IDs such as +`iqm.circuit`; the vendor component is not a reverse domain name. Use the following replacements for the removed enum values: @@ -97,14 +100,32 @@ multi-program submission API. bit outputs. OpenQASM 2 uses `creg` declarations in source order. OpenQASM 3 uses explicit bit-valued outputs, or the language's implicit outputs when none are declared. Both use increasing bit indices within each declaration. QIR uses -primitive result-recording call order. The result width is independent of the -device site count. A provider returns `QDMI_ERROR_NOTSUPPORTED` when an output -cannot be represented losslessly as a fixed-width bit string. -`QDMI_JOB_RESULT_PROGRAMOUTPUT` returns a format-defined output byte sequence -when a flat bit result cannot represent the payload result. A QIR specification -that defines an output schema uses a complete output-schema stream for every -shot. Older QIR or provider-defined descriptors can return -`QDMI_ERROR_NOTSUPPORTED`. The byte sequence need not be NUL-terminated. +primitive result-recording call order. These rules assign logical slots from +zero. Strings write the highest slot first and slot zero at the right, so slot +values `[1, 0, 0]` produce `"001"`. The payload schema owns the slots; their +width and order are independent of device sites. Logical qubit zero remains the +least-significant basis bit for state and probability results. A provider +returns `QDMI_ERROR_NOTSUPPORTED` when an output cannot be represented +losslessly as a fixed-width bit string. `QDMI_JOB_RESULT_PROGRAMOUTPUT` returns +a format-defined output byte sequence when a flat bit result cannot represent +the payload result. A QIR specification that defines an output schema uses a +complete output-schema stream for every shot. Older QIR or provider-defined +descriptors can return `QDMI_ERROR_NOTSUPPORTED`. The byte sequence need not be +NUL-terminated. + +On the client interface, job properties report the exact descriptor submitted by +the client. On the device interface, they report the descriptor executed by the +device. A translating driver must retain this mapping, including for job +retrieval, or reject retrieval with `QDMI_ERROR_NOTSUPPORTED`. A retrieved job +can report its historical descriptor after the device stops advertising that +descriptor. + +Each QDMI enum with `CUSTOM` members reserves the inclusive range from +`999999995` through `INT32_MAX` for provider-defined values. The existing +`CUSTOM1` through `CUSTOM5` names remain aliases for the first five values. +Values between an enum's regular `MAX` member and `999999995` are invalid; +unrecognized values in the custom range are valid inputs that return +`QDMI_ERROR_NOTSUPPORTED`. ### Required device job retrieval symbol diff --git a/examples/device/src/cxx_device.cpp b/examples/device/src/cxx_device.cpp index 2e655ffa..70ebb7a1 100644 --- a/examples/device/src/cxx_device.cpp +++ b/examples/device/src/cxx_device.cpp @@ -264,7 +264,7 @@ constexpr std::array QASM2_FEATURES{ QDMI_PROGRAM_CONSTRAINT_MAX_CONTROL_FLOW_NESTING_DEPTH, .constraint_value = 1}}}; -constexpr std::string_view FLAT_SHOT_OUTPUT{"01"}; +constexpr std::string_view FLAT_SHOT_OUTPUT{"001"}; constexpr std::string_view QIR_PROGRAM_OUTPUT_HEADER = "HEADER\tschema_id\tordered\n" "HEADER\tschema_version\t2.1\n"; @@ -279,13 +279,6 @@ constexpr std::string_view QIR_PROGRAM_OUTPUT_SHOT = "OUTPUT\tRESULT\t1\n" "END\t0\n"; -[[nodiscard]] bool Same_format(const QDMI_Program_Format &lhs, - const QDMI_Program_Format &rhs) { - return lhs.version == rhs.version && lhs.encoding == rhs.encoding && - std::ranges::equal(lhs.id, rhs.id) && - std::ranges::equal(lhs.profile, rhs.profile); -} - [[nodiscard]] bool Valid_format(const QDMI_Program_Format &format) { const auto canonical = [](const auto &text) { const auto nul = std::ranges::find(text, '\0'); @@ -316,8 +309,9 @@ constexpr std::string_view QIR_PROGRAM_OUTPUT_SHOT = [[nodiscard]] bool Supported_format(const QDMI_Program_Format &format) { return std::ranges::any_of( - SUPPORTED_PROGRAM_FORMATS, - [&](const auto &candidate) { return Same_format(format, candidate); }); + SUPPORTED_PROGRAM_FORMATS, [&](const auto &candidate) { + return QDMI_program_format_equal(&format, &candidate) != 0; + }); } } // namespace @@ -423,11 +417,7 @@ int CXX_QDMI_device_session_set_parameter(CXX_QDMI_Device_Session session, size_t size, const void *value) { if (session == nullptr || (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)) { + param < QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } if (session->status != CXX_QDMI_DEVICE_SESSION_STATUS::ALLOCATED) { @@ -476,11 +466,7 @@ int CXX_QDMI_device_job_set_parameter(CXX_QDMI_Device_Job job, const size_t size, const void *value) { if (job == nullptr || (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)) { + param < QDMI_DEVICE_JOB_PARAMETER_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } if (job->status != QDMI_JOB_STATUS_CREATED) { @@ -504,8 +490,19 @@ int CXX_QDMI_device_job_set_parameter(CXX_QDMI_Device_Job job, return QDMI_SUCCESS; case QDMI_DEVICE_JOB_PARAMETER_PROGRAM: if (value != nullptr) { - job->program = new char[size]; - memcpy(job->program, value, size); + if (!Valid_format(job->format)) { + return QDMI_ERROR_BADSTATE; + } + const auto *bytes = static_cast(value); + if (job->format.encoding == QDMI_PROGRAM_ENCODING_TEXT && + (bytes[size - 1U] != '\0' || + std::memchr(bytes, '\0', size - 1U) != nullptr)) { + return QDMI_ERROR_INVALIDARGUMENT; + } + auto *copy = new char[size]; + std::memcpy(copy, value, size); + delete[] static_cast(job->program); + job->program = copy; } return QDMI_SUCCESS; case QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM: @@ -524,11 +521,7 @@ int CXX_QDMI_device_job_query_property(CXX_QDMI_Device_Job job, size_t *size_ret) { if (job == nullptr || (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)) { + prop < QDMI_DEVICE_JOB_PROPERTY_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } const auto str = std::to_string(job->id); @@ -836,10 +829,7 @@ int CXX_QDMI_device_job_get_results(CXX_QDMI_Device_Job job, size_t *size_ret) { if (job == nullptr || job->status != QDMI_JOB_STATUS_DONE || (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)) { + (result >= QDMI_JOB_RESULT_MAX && result < QDMI_JOB_RESULT_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } switch (result) { @@ -873,11 +863,7 @@ int CXX_QDMI_device_session_query_device_property( const size_t size, void *value, size_t *size_ret) { if (session == nullptr || (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)) { + prop < QDMI_DEVICE_PROPERTY_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } if (session->status != CXX_QDMI_DEVICE_SESSION_STATUS::INITIALIZED) { @@ -954,7 +940,7 @@ int CXX_QDMI_device_session_query_program_features( std::ranges::copy(features, value); return QDMI_SUCCESS; }; - if (Same_format(*format, QASM2_FORMAT)) { + if (QDMI_program_format_equal(format, &QASM2_FORMAT) != 0) { return copy(QASM2_FEATURES); } if (size_ret != nullptr) { @@ -970,11 +956,7 @@ int CXX_QDMI_device_session_query_site_property(CXX_QDMI_Device_Session session, size_t *size_ret) { if (session == nullptr || 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)) { + (prop >= QDMI_SITE_PROPERTY_MAX && prop < QDMI_SITE_PROPERTY_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, site->id, prop, @@ -998,11 +980,7 @@ int CXX_QDMI_device_session_query_operation_property( (params != nullptr && num_params == 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)) { + prop < QDMI_OPERATION_PROPERTY_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } // General properties diff --git a/examples/driver/qdmi_example_driver.cpp b/examples/driver/qdmi_example_driver.cpp index 079b03dd..0152190f 100644 --- a/examples/driver/qdmi_example_driver.cpp +++ b/examples/driver/qdmi_example_driver.cpp @@ -381,11 +381,7 @@ int QDMI_session_set_parameter(QDMI_Session session, const void *value) { if (session == nullptr || (value != nullptr && size == 0) || (param >= QDMI_SESSION_PARAMETER_MAX && - param != QDMI_SESSION_PARAMETER_CUSTOM1 && - param != QDMI_SESSION_PARAMETER_CUSTOM2 && - param != QDMI_SESSION_PARAMETER_CUSTOM3 && - param != QDMI_SESSION_PARAMETER_CUSTOM4 && - param != QDMI_SESSION_PARAMETER_CUSTOM5)) { + param < QDMI_SESSION_PARAMETER_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } if (session->status != QDMI_SESSION_STATUS::ALLOCATED) { @@ -406,11 +402,7 @@ int QDMI_session_query_session_property(QDMI_Session session, void *value, size_t *size_ret) { if (session == nullptr || (value != nullptr && size == 0) || (prop >= QDMI_SESSION_PROPERTY_MAX && - prop != QDMI_SESSION_PROPERTY_CUSTOM1 && - prop != QDMI_SESSION_PROPERTY_CUSTOM2 && - prop != QDMI_SESSION_PROPERTY_CUSTOM3 && - prop != QDMI_SESSION_PROPERTY_CUSTOM4 && - prop != QDMI_SESSION_PROPERTY_CUSTOM5)) { + prop < QDMI_SESSION_PROPERTY_CUSTOM1)) { return QDMI_ERROR_INVALIDARGUMENT; } diff --git a/include/qdmi/client.h b/include/qdmi/client.h index 62454d29..cac05772 100644 --- a/include/qdmi/client.h +++ b/include/qdmi/client.h @@ -180,7 +180,7 @@ enum QDMI_SESSION_PARAMETER_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_SESSION_PARAMETER_CUSTOM1 = 999999995, + QDMI_SESSION_PARAMETER_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_SESSION_PARAMETER_CUSTOM1 QDMI_SESSION_PARAMETER_CUSTOM2 = 999999996, /// @see QDMI_SESSION_PARAMETER_CUSTOM1 @@ -295,7 +295,7 @@ enum QDMI_SESSION_PROPERTY_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_SESSION_PROPERTY_CUSTOM1 = 999999995, + QDMI_SESSION_PROPERTY_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_SESSION_PROPERTY_CUSTOM1 QDMI_SESSION_PROPERTY_CUSTOM2 = 999999996, /// @see QDMI_SESSION_PROPERTY_CUSTOM1 @@ -442,8 +442,9 @@ int QDMI_device_query_device_property(QDMI_Device device, * @brief Query the complete optional feature guarantees for one exact program * format. * @param[in] device The device to query. Must not be @c NULL. - * @param[in] format A descriptor returned by @ref - * QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS. Must not be @c NULL. + * @param[in] format A canonical descriptor equal to one returned by @ref + * QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS. The value may be reconstructed + * by the caller. Must not be @c NULL. * @param[in] size The size of @p value in bytes. Ignored when @p value is * @c NULL. * @param[out] value Storage for a list of @ref QDMI_Program_Feature records, @@ -610,7 +611,6 @@ int QDMI_device_query_operation_property( * @brief Provides functions to manage client-side jobs. * @details A job is a task submitted by a client to a device for execution. * Most jobs are quantum circuits to be executed on a quantum device. - * However, jobs can also be a different type of task, such as calibration. * * The typical workflow for a client job is as follows: * - Create a job with @ref QDMI_device_create_job. @@ -670,6 +670,12 @@ int QDMI_device_create_job(QDMI_Device device, QDMI_Job *job); * credential. Parameters cannot be set on a retrieved job, and a retrieved * job cannot be submitted again. * + * A translating driver must restore the exact client-submitted descriptor and + * its mapping to the executed descriptor. The client descriptor may no longer + * appear in the device's current supported-format list. A driver that cannot + * reconstruct this information losslessly returns @ref + * QDMI_ERROR_NOTSUPPORTED. + * * @param[in] device The device from which to retrieve the job. Must not be @c * NULL. * @param[in] job_id The nonempty, null-terminated ID returned by @@ -702,13 +708,18 @@ enum QDMI_JOB_PARAMETER_T { * @details This parameter is required. If the device does not support the * specified program format, it is up to the driver to decide whether to * return @ref QDMI_ERROR_NOTSUPPORTED from @ref QDMI_job_set_parameter or to - * convert the program to a supported format. + * convert the program to a supported format. A translating driver retains the + * client-submitted descriptor so that properties and retrieved jobs report + * the client value rather than the executed device value. */ QDMI_JOB_PARAMETER_PROGRAMFORMAT = 0, /** * @brief `void*` The program to be executed. * @details This parameter is required. The program must be in the format * specified by the @ref QDMI_JOB_PARAMETER_PROGRAMFORMAT parameter. + * A text program contains exactly one trailing NUL and no earlier NUL; @c + * size includes that NUL. A binary program is a nonempty arbitrary byte + * sequence. The driver validates this representation before any conversion. * If the program is invalid, the @ref QDMI_job_set_parameter function * must return @ref QDMI_ERROR_INVALIDARGUMENT. If the program is valid, but * the device cannot execute it, the @ref QDMI_job_set_parameter function must @@ -734,7 +745,7 @@ enum QDMI_JOB_PARAMETER_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_JOB_PARAMETER_CUSTOM1 = 999999995, + QDMI_JOB_PARAMETER_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_JOB_PARAMETER_CUSTOM1 QDMI_JOB_PARAMETER_CUSTOM2 = 999999996, /// @see QDMI_JOB_PARAMETER_CUSTOM1 @@ -822,8 +833,9 @@ enum QDMI_JOB_PROPERTY_T { QDMI_JOB_PROPERTY_ID = 0, /** * @brief @ref QDMI_Program_Format The format of the program to be executed. - * @note This property returns the value of the @ref - * QDMI_JOB_PARAMETER_PROGRAMFORMAT parameter. + * @note This property returns the exact client-submitted value of @ref + * QDMI_JOB_PARAMETER_PROGRAMFORMAT, including on a translated or retrieved + * job. The executed device descriptor can differ. */ QDMI_JOB_PROPERTY_PROGRAMFORMAT = 1, /** @@ -868,7 +880,7 @@ enum QDMI_JOB_PROPERTY_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_JOB_PROPERTY_CUSTOM1 = 999999995, + QDMI_JOB_PROPERTY_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_JOB_PROPERTY_CUSTOM1 QDMI_JOB_PROPERTY_CUSTOM2 = 999999996, /// @see QDMI_JOB_PROPERTY_CUSTOM1 diff --git a/include/qdmi/constants.h b/include/qdmi/constants.h index 73715196..ad741a37 100644 --- a/include/qdmi/constants.h +++ b/include/qdmi/constants.h @@ -25,6 +25,7 @@ #ifndef QDMI_CONSTANTS_H #define QDMI_CONSTANTS_H +#include #include #ifdef __cplusplus @@ -55,6 +56,19 @@ enum QDMI_STATUS { QDMI_ERROR_TIMEOUT = -11, ///< Operation timed out. }; +/** + * @brief First value in every provider-defined enum range. + * @details Every value from @ref QDMI_CUSTOM_ENUM_VALUE_MIN through @ref + * QDMI_CUSTOM_ENUM_VALUE_MAX is syntactically valid. The `CUSTOM1` through + * `CUSTOM5` members of each extensible enum preserve names for the first five + * values. Values from such an enum's regular `MAX` member up to, but excluding, + * @ref QDMI_CUSTOM_ENUM_VALUE_MIN are invalid. An implementation returns @ref + * QDMI_ERROR_NOTSUPPORTED for a valid custom value that it does not support. + */ +#define QDMI_CUSTOM_ENUM_VALUE_MIN 999999995 +/** @brief Last value in every provider-defined enum range. */ +#define QDMI_CUSTOM_ENUM_VALUE_MAX INT32_MAX + /** * @brief Enum of the device session parameters that can be set via @ref * QDMI_device_session_set_parameter. @@ -144,7 +158,7 @@ enum QDMI_DEVICE_SESSION_PARAMETER_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 = 999999995, + QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2 = 999999996, /// @see QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 @@ -177,6 +191,9 @@ enum QDMI_DEVICE_JOB_PARAMETER_T { * @brief `void*` The program to be executed. * @details This parameter is required. The program must be in the format * specified by the @ref QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT parameter. + * A text program contains exactly one trailing NUL and no earlier NUL; @c + * size includes that NUL. A binary program is a nonempty arbitrary byte + * sequence. * If the program is invalid, the @ref QDMI_device_job_set_parameter function * must return @ref QDMI_ERROR_INVALIDARGUMENT. If the program is valid, but * the device cannot execute it, the @ref QDMI_device_job_set_parameter @@ -203,7 +220,7 @@ enum QDMI_DEVICE_JOB_PARAMETER_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 = 999999995, + QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 QDMI_DEVICE_JOB_PARAMETER_CUSTOM2 = 999999996, /// @see QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 @@ -238,8 +255,9 @@ enum QDMI_DEVICE_JOB_PROPERTY_T { QDMI_DEVICE_JOB_PROPERTY_ID = 0, /** * @brief @ref QDMI_Program_Format The format of the program to be executed. - * @note This property returns the value of the @ref - * QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT parameter. + * @note This property returns the descriptor executed by the device. A driver + * can expose a different client-submitted descriptor when it converts the + * payload before setting @ref QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT. */ QDMI_DEVICE_JOB_PROPERTY_PROGRAMFORMAT = 1, /** @@ -284,7 +302,7 @@ enum QDMI_DEVICE_JOB_PROPERTY_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 = 999999995, + QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 QDMI_DEVICE_JOB_PROPERTY_CUSTOM2 = 999999996, /// @see QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 @@ -473,7 +491,7 @@ enum QDMI_DEVICE_PROPERTY_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_DEVICE_PROPERTY_CUSTOM1 = 999999995, + QDMI_DEVICE_PROPERTY_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_DEVICE_PROPERTY_CUSTOM1 QDMI_DEVICE_PROPERTY_CUSTOM2 = 999999996, /// @see QDMI_DEVICE_PROPERTY_CUSTOM1 @@ -708,7 +726,7 @@ enum QDMI_SITE_PROPERTY_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_SITE_PROPERTY_CUSTOM1 = 999999995, + QDMI_SITE_PROPERTY_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_SITE_PROPERTY_CUSTOM1 QDMI_SITE_PROPERTY_CUSTOM2 = 999999996, /// @see QDMI_SITE_PROPERTY_CUSTOM1 @@ -858,7 +876,7 @@ enum QDMI_OPERATION_PROPERTY_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_OPERATION_PROPERTY_CUSTOM1 = 999999995, + QDMI_OPERATION_PROPERTY_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_OPERATION_PROPERTY_CUSTOM1 QDMI_OPERATION_PROPERTY_CUSTOM2 = 999999996, /// @see QDMI_OPERATION_PROPERTY_CUSTOM1 @@ -923,8 +941,10 @@ typedef enum QDMI_JOB_STATUS_T QDMI_Job_Status; /** @brief Encoding of a submitted payload. */ enum QDMI_PROGRAM_ENCODING_T { - QDMI_PROGRAM_ENCODING_TEXT = 1, ///< NUL-terminated text. - QDMI_PROGRAM_ENCODING_BINARY = 2 ///< Arbitrary bytes. + /// Text with exactly one trailing NUL and no earlier NUL. + QDMI_PROGRAM_ENCODING_TEXT = 1, + /// A nonempty arbitrary byte sequence. + QDMI_PROGRAM_ENCODING_BINARY = 2 }; /// Program encoding type. @@ -938,9 +958,14 @@ typedef enum QDMI_PROGRAM_ENCODING_T QDMI_Program_Encoding; * compatibility between versions, profiles, or encodings. * * The `id` and `profile` arrays must be NUL-terminated, and every byte after - * the first NUL must be zero. IDs are case-sensitive. QDMI reserves + * the first NUL must be zero. Every API compares descriptor values, not their + * addresses. A caller may reconstruct a canonical value from its fields; use + * @c QDMI_program_format_equal to compare two values. IDs are case-sensitive. + * QDMI reserves * unqualified IDs for standard formats. The standard IDs are `openqasm` and - * `qir`. Vendor formats must use a namespaced ID such as `com.vendor.format`. + * `qir`. Vendor formats use `.` IDs, such as + * `iqm.circuit`. The vendor component is an identifier, not a reverse domain + * name. * QDMI does not define vendor-format versions, profiles, wire formats, or * result semantics. Providers must document each vendor descriptor and its * payload and result contract. An empty profile identifies a format without a @@ -996,6 +1021,50 @@ typedef struct QDMI_PROGRAM_FORMAT_T { char profile[QDMI_PROGRAM_ID_SIZE]; ///< NUL-terminated profile ID. } QDMI_Program_Format; +#ifdef __cplusplus +static_assert(sizeof(QDMI_Program_Format) == 136U); +static_assert(alignof(QDMI_Program_Format) == 4U); +static_assert(offsetof(QDMI_Program_Format, version) == 0U); +static_assert(offsetof(QDMI_Program_Format, encoding) == 4U); +static_assert(offsetof(QDMI_Program_Format, id) == 8U); +static_assert(offsetof(QDMI_Program_Format, profile) == 72U); +#else +_Static_assert(sizeof(QDMI_Program_Format) == 136U, + "QDMI_Program_Format must be 136 bytes"); +_Static_assert(_Alignof(QDMI_Program_Format) == 4U, + "QDMI_Program_Format must have four-byte alignment"); +_Static_assert(offsetof(QDMI_Program_Format, version) == 0U, + "QDMI_Program_Format.version must start at byte 0"); +_Static_assert(offsetof(QDMI_Program_Format, encoding) == 4U, + "QDMI_Program_Format.encoding must start at byte 4"); +_Static_assert(offsetof(QDMI_Program_Format, id) == 8U, + "QDMI_Program_Format.id must start at byte 8"); +_Static_assert(offsetof(QDMI_Program_Format, profile) == 72U, + "QDMI_Program_Format.profile must start at byte 72"); +#endif + +/** + * @brief Compare two exact program-format values. + * @param[in] lhs The first descriptor, or @c NULL. + * @param[in] rhs The second descriptor, or @c NULL. + * @return Nonzero if every field and array byte is equal; otherwise, zero. + */ +static inline int +QDMI_program_format_equal(const QDMI_Program_Format *const lhs, + const QDMI_Program_Format *const rhs) { + if (lhs == NULL || rhs == NULL || lhs->version != rhs->version || + lhs->encoding != rhs->encoding) { + return 0; + } + for (size_t index = 0U; index < QDMI_PROGRAM_ID_SIZE; ++index) { + if (lhs->id[index] != rhs->id[index] || + lhs->profile[index] != rhs->profile[index]) { + return 0; + } + } + return 1; +} + /// Maximum bytes, including the terminating NUL, in a feature ID. #define QDMI_PROGRAM_FEATURE_ID_SIZE 64U @@ -1157,7 +1226,8 @@ enum QDMI_JOB_RESULT_T { * @brief `char*` (string) The results of the individual shots as a * comma-separated list. * @details Each bit string contains every flat bit output declared by the - * submitted payload. The first output in the order below is the leftmost bit: + * submitted payload. The following rules assign logical output slots starting + * at zero: * - OpenQASM 2 uses `creg` declarations in source order and increasing bit * index within each declaration. * - OpenQASM 3 uses bit-valued output declarations in source order and @@ -1167,14 +1237,17 @@ enum QDMI_JOB_RESULT_T { * - QIR uses primitive result-recording calls in execution order. A result * array contributes its elements in memory order. Container recording * calls do not add bits. + * The string writes the highest-numbered slot first and slot zero at the + * right. For example, logical slot values `[1, 0, 0]` produce `"001"`. * If the payload output cannot be represented losslessly as one fixed-width * bit string per shot, queries for shots and histogram results return @ref * QDMI_ERROR_NOTSUPPORTED. Clients can query @ref * QDMI_JOB_RESULT_PROGRAMOUTPUT when the submitted descriptor defines a * native output representation. * - * The width is independent of the number of device sites. For example, - * "0010,1101,0101" represents three shots of four declared bit outputs. + * The payload output schema owns the slots. Their width and order are + * independent of the device sites and physical-site order. For example, + * `"0010,1101,0101"` represents three shots of four declared bit outputs. */ QDMI_JOB_RESULT_SHOTS = 0, /** @@ -1198,16 +1271,18 @@ enum QDMI_JOB_RESULT_T { /** * @brief `double*` (`double` list) The state vector of the result. * @details The complex amplitudes are stored as a list of real and imaginary - * parts. The real part of the amplitude is at index `2n` and the imaginary - * part is at index `2n+1`. For example, the state vector of a 2-qubit system + * parts. Logical qubit zero is the least-significant bit of basis index `n`. + * The real part of the amplitude is at index `2n` and the imaginary part is + * at index `2n+1`. For example, the state vector of a 2-qubit system * with amplitudes `(0.5, 0.5), (0.5, -0.5), (-0.5, 0.5), (-0.5, -0.5)` would * be represented as `{0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5}`. */ QDMI_JOB_RESULT_STATEVECTOR_DENSE = 3, /** * @brief `double*` (`double` list) The probabilities of the result. - * @details The probabilities are stored as a list of real numbers. The - * probability of the state with index `n` is at index `n` in the list. For + * @details The probabilities are stored as a list of real numbers. Logical + * qubit zero is the least-significant bit of basis index `n`. The probability + * of that state is at index `n` in the list. For * example, the probabilities of a 2-qubit system with states `00, 01, 10, 11` * would be represented as `{0.25, 0.25, 0.25, 0.25}`. */ @@ -1217,7 +1292,8 @@ enum QDMI_JOB_RESULT_T { * @details The sparse state vector is represented as a key-value mapping. * This mapping is returned as a list of keys and an equal-length list of * values. The corresponding partners of keys and values can be found at the - * same index in the lists. + * same index in the lists. Keys write the highest-numbered logical qubit at + * the left and logical qubit zero at the right. */ QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS = 5, /** @@ -1235,7 +1311,8 @@ enum QDMI_JOB_RESULT_T { * @details The sparse probabilities are represented as a key-value mapping. * This mapping is returned as a list of keys and an equal-length list of * values. The corresponding partners of keys and values can be found at the - * same index in the lists. + * same index in the lists. Keys write the highest-numbered logical qubit at + * the left and logical qubit zero at the right. */ QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS = 7, /** @@ -1273,7 +1350,7 @@ enum QDMI_JOB_RESULT_T { * @attention The value of this enum member must not be changed to maintain * binary compatibility. */ - QDMI_JOB_RESULT_CUSTOM1 = 999999995, + QDMI_JOB_RESULT_CUSTOM1 = QDMI_CUSTOM_ENUM_VALUE_MIN, /// @see QDMI_JOB_RESULT_CUSTOM1 QDMI_JOB_RESULT_CUSTOM2 = 999999996, /// @see QDMI_JOB_RESULT_CUSTOM1 diff --git a/include/qdmi/device.h b/include/qdmi/device.h index 827847b3..88413a94 100644 --- a/include/qdmi/device.h +++ b/include/qdmi/device.h @@ -290,8 +290,9 @@ QDMI_EXPORT int QDMI_device_session_query_device_property( * format. * @param[in] session The initialized session used for the query. Must not be * @c NULL. - * @param[in] format A descriptor returned by @ref - * QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS. Must not be @c NULL. + * @param[in] format A canonical descriptor equal to one returned by @ref + * QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS. The value may be reconstructed + * by the caller. Must not be @c NULL. * @param[in] size The size of @p value in bytes. Ignored when @p value is * @c NULL. * @param[out] value Storage for a list of @ref QDMI_Program_Feature records, @@ -454,7 +455,6 @@ QDMI_EXPORT int QDMI_device_session_query_operation_property( * @brief Provides functions to manage jobs on a device. * @details A job is a task submitted to a device for execution. * Most jobs are quantum circuits to be executed on a quantum device. - * However, jobs can also be a different type of task, such as calibration. * * The typical workflow for a device job is as follows: * - Create a job with @ref QDMI_device_session_create_device_job. @@ -520,6 +520,11 @@ QDMI_device_session_create_device_job(QDMI_Device_Session session, * Parameters cannot be set on a retrieved job, and a retrieved job cannot be * submitted again. * + * The retrieved job's properties describe the historical execution. Its exact + * program descriptor may no longer appear in the device's current + * supported-format list. A device that cannot reconstruct the historical + * descriptor losslessly returns @ref QDMI_ERROR_NOTSUPPORTED. + * * @param[in] session The initialized session with which to retrieve the job. * Must not be @c NULL. * @param[in] job_id The nonempty, null-terminated ID returned by diff --git a/test/test_qdmi.cpp b/test/test_qdmi.cpp index 39af8d42..8d67f93c 100644 --- a/test/test_qdmi.cpp +++ b/test/test_qdmi.cpp @@ -50,6 +50,10 @@ QDMI_Program_Feature QDMI_test_unconstrained_program_feature(void); namespace { static_assert(sizeof(QDMI_Program_Format) == 136U); static_assert(sizeof(QDMI_Program_Feature) == 144U); +static_assert(QDMI_CUSTOM_ENUM_VALUE_MIN == 999999995); +static_assert(QDMI_CUSTOM_ENUM_VALUE_MAX == INT32_MAX); +static_assert(QDMI_JOB_RESULT_CUSTOM1 == QDMI_CUSTOM_ENUM_VALUE_MIN); +static_assert(QDMI_JOB_RESULT_CUSTOM5 == QDMI_CUSTOM_ENUM_VALUE_MIN + 4); static_assert(QDMI_VERSION_MAJOR(QDMI_MAKE_VERSION(2, 1, 3)) == 2U); static_assert(QDMI_VERSION_MINOR(QDMI_MAKE_VERSION(2, 1, 3)) == 1U); static_assert(QDMI_VERSION_PATCH(QDMI_MAKE_VERSION(2, 1, 3)) == 3U); @@ -61,7 +65,7 @@ static_assert(CPP_UNCONSTRAINED_FEATURE.value == 0U); static_assert(CPP_UNCONSTRAINED_FEATURE.constraint_id[0] == '\0'); static_assert(CPP_UNCONSTRAINED_FEATURE.constraint_value == 0U); -constexpr std::string_view FLAT_SHOT_OUTPUT{"01"}; +constexpr std::string_view FLAT_SHOT_OUTPUT{"001"}; constexpr std::string_view EXPECTED_QIR_PROGRAM_OUTPUT = "HEADER\tschema_id\tordered\n" "HEADER\tschema_version\t2.1\n" @@ -100,13 +104,6 @@ constexpr QDMI_Program_Format QIR_BASE_BINARY_FORMAT{ .id = "qir", .profile = "base"}; -bool Same_format(const QDMI_Program_Format &lhs, - const QDMI_Program_Format &rhs) { - return lhs.version == rhs.version && lhs.encoding == rhs.encoding && - std::ranges::equal(lhs.id, rhs.id) && - std::ranges::equal(lhs.profile, rhs.profile); -} - /// Hash function for a pair struct Pair_hash { template @@ -125,6 +122,20 @@ TEST(QDMIConstantsTest, UnconstrainedProgramFeatureMacroIsCCompatible) { EXPECT_EQ(feature.constraint_value, 0U); } +TEST(QDMIConstantsTest, ProgramFormatEqualityUsesCanonicalValues) { + constexpr QDMI_Program_Format reconstructed{ + .version = QDMI_MAKE_VERSION(2, 0, 0), + .encoding = QDMI_PROGRAM_ENCODING_TEXT, + .id = "openqasm", + .profile = ""}; + EXPECT_NE(QDMI_program_format_equal(&QASM2_FORMAT, &reconstructed), 0); + EXPECT_EQ(QDMI_program_format_equal(nullptr, &reconstructed), 0); + + auto noncanonical = reconstructed; + noncanonical.profile[1] = 'x'; + EXPECT_EQ(QDMI_program_format_equal(&reconstructed, &noncanonical), 0); +} + // Instantiate the test suite with different parameters INSTANTIATE_TEST_SUITE_P( QDMIDevice, @@ -356,6 +367,12 @@ TEST_P(QDMIImplementationTest, QueryGatePropertiesForEachGate) { device, op, 0, nullptr, 0, nullptr, QDMI_OPERATION_PROPERTY_CUSTOM5, 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ(QDMI_device_query_operation_property( + device, op, 0, nullptr, 0, nullptr, + static_cast( + QDMI_OPERATION_PROPERTY_CUSTOM5 + 1), + 0, nullptr, nullptr), + QDMI_ERROR_NOTSUPPORTED); } } @@ -442,6 +459,12 @@ TEST_P(QDMIImplementationTest, QuerySiteProperties) { QDMI_SITE_PROPERTY_CUSTOM5, 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ( + QDMI_device_query_site_property( + device, site, + static_cast(QDMI_SITE_PROPERTY_CUSTOM5 + 1), 0, + nullptr, nullptr), + QDMI_ERROR_NOTSUPPORTED); } } @@ -516,6 +539,11 @@ TEST_P(QDMIImplementationTest, QueryDeviceProperties) { EXPECT_EQ(QDMI_device_query_program_features(device, &invalid_qir, 0, nullptr, nullptr), QDMI_ERROR_INVALIDARGUMENT); + auto noncanonical = QASM2_FORMAT; + noncanonical.id[sizeof("openqasm")] = 'x'; + EXPECT_EQ(QDMI_device_query_program_features(device, &noncanonical, 0, + nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); constexpr QDMI_Program_Format invalid_qpy{ .version = QDMI_MAKE_VERSION(1, 0, 0), .encoding = QDMI_PROGRAM_ENCODING_BINARY, @@ -527,7 +555,7 @@ TEST_P(QDMIImplementationTest, QueryDeviceProperties) { constexpr QDMI_Program_Format vendor_qpy{ .version = QDMI_MAKE_VERSION(1, 0, 0), .encoding = QDMI_PROGRAM_ENCODING_BINARY, - .id = "com.vendor.qpy", + .id = "vendor.qpy", .profile = ""}; EXPECT_EQ(QDMI_device_query_program_features(device, &vendor_qpy, 0, nullptr, nullptr), @@ -568,6 +596,17 @@ TEST_P(QDMIImplementationTest, QueryDeviceProperties) { EXPECT_EQ(QDMI_device_query_device_property( device, QDMI_DEVICE_PROPERTY_CUSTOM5, 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ( + QDMI_device_query_device_property( + device, + static_cast(QDMI_DEVICE_PROPERTY_CUSTOM5 + 1), + 0, nullptr, nullptr), + QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ(QDMI_device_query_device_property( + device, + static_cast(QDMI_CUSTOM_ENUM_VALUE_MAX), + 0, nullptr, nullptr), + QDMI_ERROR_NOTSUPPORTED); } TEST_P(QDMIImplementationTest, JobLifecycle) { @@ -640,6 +679,16 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_CUSTOM5, 0, nullptr), QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ(QDMI_job_set_parameter( + job, + static_cast(QDMI_JOB_PARAMETER_CUSTOM5 + 1), + 0, nullptr), + QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ(QDMI_job_set_parameter( + job, + static_cast(QDMI_JOB_PARAMETER_MAX + 1), 0, + nullptr), + QDMI_ERROR_INVALIDARGUMENT); format = QASM2_FORMAT; EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, @@ -653,7 +702,7 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { &size), QDMI_SUCCESS); EXPECT_EQ(size, sizeof(QDMI_Program_Format)); - EXPECT_TRUE(Same_format(format, QASM2_FORMAT)); + EXPECT_NE(QDMI_program_format_equal(&format, &QASM2_FORMAT), 0); size_t shots = 5; EXPECT_EQ(QDMI_job_set_parameter(nullptr, QDMI_JOB_PARAMETER_SHOTSNUM, @@ -674,6 +723,15 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { sizeof(size_t), &shots, nullptr), QDMI_SUCCESS); EXPECT_EQ(shots, 5); + EXPECT_EQ(QDMI_job_query_property( + job, + static_cast(QDMI_JOB_PROPERTY_CUSTOM5 + 1), + 0, nullptr, nullptr), + QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ(QDMI_job_query_property( + job, static_cast(QDMI_JOB_PROPERTY_MAX + 1), + 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); // Queue position is optional and is not supported by the example device. EXPECT_EQ(QDMI_job_query_property(job, QDMI_JOB_PROPERTY_QUEUEPOSITION, 0, nullptr, nullptr), @@ -701,6 +759,50 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { QDMI_job_free(job); } +TEST_P(QDMIImplementationTest, ValidatesProgramPayloadEncoding) { + if (mode == TEST_SESSION_MODE::READONLY) { + GTEST_SKIP() << "Skipping test for read-only session"; + } + + QDMI_Job text_job = nullptr; + ASSERT_EQ(QDMI_device_create_job(device, &text_job), QDMI_SUCCESS); + constexpr std::array valid_text{'x', 'y', '\0'}; + EXPECT_EQ(QDMI_job_set_parameter(text_job, QDMI_JOB_PARAMETER_PROGRAM, + valid_text.size(), valid_text.data()), + QDMI_ERROR_BADSTATE); + ASSERT_EQ(QDMI_job_set_parameter(text_job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(QASM2_FORMAT), &QASM2_FORMAT), + QDMI_SUCCESS); + constexpr std::array unterminated_text{'x', 'y'}; + EXPECT_EQ(QDMI_job_set_parameter(text_job, QDMI_JOB_PARAMETER_PROGRAM, + unterminated_text.size(), + unterminated_text.data()), + QDMI_ERROR_INVALIDARGUMENT); + constexpr std::array embedded_nul{'x', '\0', 'y', '\0'}; + EXPECT_EQ(QDMI_job_set_parameter(text_job, QDMI_JOB_PARAMETER_PROGRAM, + embedded_nul.size(), embedded_nul.data()), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_EQ(QDMI_job_set_parameter(text_job, QDMI_JOB_PARAMETER_PROGRAM, + valid_text.size(), valid_text.data()), + QDMI_SUCCESS); + QDMI_job_free(text_job); + + QDMI_Job binary_job = nullptr; + ASSERT_EQ(QDMI_device_create_job(device, &binary_job), QDMI_SUCCESS); + ASSERT_EQ(QDMI_job_set_parameter(binary_job, QDMI_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(QIR_BASE_BINARY_FORMAT), + &QIR_BASE_BINARY_FORMAT), + QDMI_SUCCESS); + constexpr std::array binary{0U, 0xFFU, 0U}; + EXPECT_EQ(QDMI_job_set_parameter(binary_job, QDMI_JOB_PARAMETER_PROGRAM, 0, + binary.data()), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_EQ(QDMI_job_set_parameter(binary_job, QDMI_JOB_PARAMETER_PROGRAM, + binary.size(), binary.data()), + QDMI_SUCCESS); + QDMI_job_free(binary_job); +} + TEST_P(QDMIImplementationTest, ToolCompile) { Tool tool(device); const auto fomac = FoMaC(device); @@ -728,10 +830,11 @@ QDMI_Job Submit_test_job(QDMI_Device dev, const size_t num_shots = 0) { OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; -creg c[2]; +creg c[3]; rx(pi/2) q[0]; cx q[0], q[1]; -measure q -> c; +measure q[0] -> c[0]; +measure q[1] -> c[1]; )"; QDMI_Job job = nullptr; EXPECT_EQ(QDMI_device_create_job(dev, &job), QDMI_SUCCESS); @@ -780,6 +883,10 @@ TEST_P(QDMIImplementationTest, GetResultsCornerCases) { EXPECT_EQ( QDMI_job_get_results(job, QDMI_JOB_RESULT_CUSTOM5, 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ(QDMI_job_get_results( + job, static_cast(QDMI_JOB_RESULT_CUSTOM5 + 1), + 0, nullptr, nullptr), + QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_PROGRAMOUTPUT, 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); @@ -1225,6 +1332,16 @@ TEST_P(QDMIImplementationTest, SessionSetParameter) { QDMI_Session session2 = nullptr; ASSERT_EQ(QDMI_session_alloc(&session2), QDMI_SUCCESS); + EXPECT_EQ(QDMI_session_set_parameter(session2, + static_cast( + QDMI_SESSION_PARAMETER_CUSTOM5 + 1), + 0, nullptr), + QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ(QDMI_session_set_parameter(session2, + static_cast( + QDMI_SESSION_PARAMETER_MAX + 1), + 0, nullptr), + QDMI_ERROR_INVALIDARGUMENT); EXPECT_EQ(QDMI_session_set_parameter(session2, QDMI_SESSION_PARAMETER_USERNAME, 1, ""), QDMI_ERROR_NOTSUPPORTED); @@ -1295,6 +1412,18 @@ TEST_P(QDMIImplementationTest, SessionQuerySessionProperty) { EXPECT_EQ(QDMI_session_query_session_property( session, QDMI_SESSION_PROPERTY_CUSTOM5, 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ( + QDMI_session_query_session_property( + session, + static_cast(QDMI_SESSION_PROPERTY_CUSTOM5 + 1), + 0, nullptr, nullptr), + QDMI_ERROR_NOTSUPPORTED); + EXPECT_EQ( + QDMI_session_query_session_property( + session, + static_cast(QDMI_SESSION_PROPERTY_MAX + 1), 0, + nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); // Must not query on an uninitialized session QDMI_Session session2 = nullptr; From 02ded4e07078f9492c24bbfc2f3d2b1694ada5c2 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 24 Aug 2026 09:22:34 +0000 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=90=9B=20Keep=20custom=20enum=20value?= =?UTF-8?q?s=20representable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a terminal enumerator to every extensible enum so the complete declared custom range is well-defined in C++. Assisted-by: GPT-5.6 Sol via Codex --- include/qdmi/client.h | 16 ++++++++++++---- include/qdmi/constants.h | 28 +++++++++++++++++++++------- test/test_qdmi.cpp | 2 ++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/include/qdmi/client.h b/include/qdmi/client.h index cac05772..850c4cbd 100644 --- a/include/qdmi/client.h +++ b/include/qdmi/client.h @@ -188,7 +188,9 @@ enum QDMI_SESSION_PARAMETER_T { /// @see QDMI_SESSION_PARAMETER_CUSTOM1 QDMI_SESSION_PARAMETER_CUSTOM4 = 999999998, /// @see QDMI_SESSION_PARAMETER_CUSTOM1 - QDMI_SESSION_PARAMETER_CUSTOM5 = 999999999 + QDMI_SESSION_PARAMETER_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_SESSION_PARAMETER_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Session parameter type. @@ -303,7 +305,9 @@ enum QDMI_SESSION_PROPERTY_T { /// @see QDMI_SESSION_PROPERTY_CUSTOM1 QDMI_SESSION_PROPERTY_CUSTOM4 = 999999998, /// @see QDMI_SESSION_PROPERTY_CUSTOM1 - QDMI_SESSION_PROPERTY_CUSTOM5 = 999999999 + QDMI_SESSION_PROPERTY_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_SESSION_PROPERTY_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Session property type. @@ -753,7 +757,9 @@ enum QDMI_JOB_PARAMETER_T { /// @see QDMI_JOB_PARAMETER_CUSTOM1 QDMI_JOB_PARAMETER_CUSTOM4 = 999999998, /// @see QDMI_JOB_PARAMETER_CUSTOM1 - QDMI_JOB_PARAMETER_CUSTOM5 = 999999999 + QDMI_JOB_PARAMETER_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_JOB_PARAMETER_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Job parameter type. @@ -888,7 +894,9 @@ enum QDMI_JOB_PROPERTY_T { /// @see QDMI_JOB_PROPERTY_CUSTOM1 QDMI_JOB_PROPERTY_CUSTOM4 = 999999998, /// @see QDMI_JOB_PROPERTY_CUSTOM1 - QDMI_JOB_PROPERTY_CUSTOM5 = 999999999 + QDMI_JOB_PROPERTY_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_JOB_PROPERTY_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Job property type. diff --git a/include/qdmi/constants.h b/include/qdmi/constants.h index ad741a37..1ebf5912 100644 --- a/include/qdmi/constants.h +++ b/include/qdmi/constants.h @@ -166,7 +166,9 @@ enum QDMI_DEVICE_SESSION_PARAMETER_T { /// @see QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4 = 999999998, /// @see QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 - QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5 = 999999999 + QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_DEVICE_SESSION_PARAMETER_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Device session parameter type. @@ -228,7 +230,9 @@ enum QDMI_DEVICE_JOB_PARAMETER_T { /// @see QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 QDMI_DEVICE_JOB_PARAMETER_CUSTOM4 = 999999998, /// @see QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 - QDMI_DEVICE_JOB_PARAMETER_CUSTOM5 = 999999999 + QDMI_DEVICE_JOB_PARAMETER_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_DEVICE_JOB_PARAMETER_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Device job parameter type. @@ -310,7 +314,9 @@ enum QDMI_DEVICE_JOB_PROPERTY_T { /// @see QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 QDMI_DEVICE_JOB_PROPERTY_CUSTOM4 = 999999998, /// @see QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 - QDMI_DEVICE_JOB_PROPERTY_CUSTOM5 = 999999999 + QDMI_DEVICE_JOB_PROPERTY_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_DEVICE_JOB_PROPERTY_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Device job property type. @@ -499,7 +505,9 @@ enum QDMI_DEVICE_PROPERTY_T { /// @see QDMI_DEVICE_PROPERTY_CUSTOM1 QDMI_DEVICE_PROPERTY_CUSTOM4 = 999999998, /// @see QDMI_DEVICE_PROPERTY_CUSTOM1 - QDMI_DEVICE_PROPERTY_CUSTOM5 = 999999999 + QDMI_DEVICE_PROPERTY_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_DEVICE_PROPERTY_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Device property type. @@ -734,7 +742,9 @@ enum QDMI_SITE_PROPERTY_T { /// @see QDMI_SITE_PROPERTY_CUSTOM1 QDMI_SITE_PROPERTY_CUSTOM4 = 999999998, /// @see QDMI_SITE_PROPERTY_CUSTOM1 - QDMI_SITE_PROPERTY_CUSTOM5 = 999999999 + QDMI_SITE_PROPERTY_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_SITE_PROPERTY_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Site property type. @@ -884,7 +894,9 @@ enum QDMI_OPERATION_PROPERTY_T { /// @see QDMI_OPERATION_PROPERTY_CUSTOM1 QDMI_OPERATION_PROPERTY_CUSTOM4 = 999999998, /// @see QDMI_OPERATION_PROPERTY_CUSTOM1 - QDMI_OPERATION_PROPERTY_CUSTOM5 = 999999999 + QDMI_OPERATION_PROPERTY_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_OPERATION_PROPERTY_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Operation property type. @@ -1358,7 +1370,9 @@ enum QDMI_JOB_RESULT_T { /// @see QDMI_JOB_RESULT_CUSTOM1 QDMI_JOB_RESULT_CUSTOM4 = 999999998, /// @see QDMI_JOB_RESULT_CUSTOM1 - QDMI_JOB_RESULT_CUSTOM5 = 999999999 + QDMI_JOB_RESULT_CUSTOM5 = 999999999, + /// The largest syntactically valid custom value. + QDMI_JOB_RESULT_CUSTOM_MAX = QDMI_CUSTOM_ENUM_VALUE_MAX }; /// Job result type. diff --git a/test/test_qdmi.cpp b/test/test_qdmi.cpp index 8d67f93c..098e8dc1 100644 --- a/test/test_qdmi.cpp +++ b/test/test_qdmi.cpp @@ -54,6 +54,8 @@ static_assert(QDMI_CUSTOM_ENUM_VALUE_MIN == 999999995); static_assert(QDMI_CUSTOM_ENUM_VALUE_MAX == INT32_MAX); static_assert(QDMI_JOB_RESULT_CUSTOM1 == QDMI_CUSTOM_ENUM_VALUE_MIN); static_assert(QDMI_JOB_RESULT_CUSTOM5 == QDMI_CUSTOM_ENUM_VALUE_MIN + 4); +static_assert(QDMI_JOB_RESULT_CUSTOM_MAX == QDMI_CUSTOM_ENUM_VALUE_MAX); +static_assert(QDMI_SESSION_PARAMETER_CUSTOM_MAX == QDMI_CUSTOM_ENUM_VALUE_MAX); static_assert(QDMI_VERSION_MAJOR(QDMI_MAKE_VERSION(2, 1, 3)) == 2U); static_assert(QDMI_VERSION_MINOR(QDMI_MAKE_VERSION(2, 1, 3)) == 1U); static_assert(QDMI_VERSION_PATCH(QDMI_MAKE_VERSION(2, 1, 3)) == 3U); From 75cfc34f0067bde6ee70b241c37947c072c27ec3 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 24 Aug 2026 09:36:39 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=9A=A8=20Mark=20intentional=20enum=20?= =?UTF-8?q?boundary=20casts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suppress Clang enum-membership warnings only at tests that exercise unnamed valid and invalid API enum values. Assisted-by: GPT-5.6 Sol via Codex --- test/test_qdmi.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/test/test_qdmi.cpp b/test/test_qdmi.cpp index 098e8dc1..114d52ed 100644 --- a/test/test_qdmi.cpp +++ b/test/test_qdmi.cpp @@ -371,6 +371,7 @@ TEST_P(QDMIImplementationTest, QueryGatePropertiesForEachGate) { QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_device_query_operation_property( device, op, 0, nullptr, 0, nullptr, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast( QDMI_OPERATION_PROPERTY_CUSTOM5 + 1), 0, nullptr, nullptr), @@ -464,6 +465,7 @@ TEST_P(QDMIImplementationTest, QuerySiteProperties) { EXPECT_EQ( QDMI_device_query_site_property( device, site, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast(QDMI_SITE_PROPERTY_CUSTOM5 + 1), 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); @@ -601,6 +603,7 @@ TEST_P(QDMIImplementationTest, QueryDeviceProperties) { EXPECT_EQ( QDMI_device_query_device_property( device, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast(QDMI_DEVICE_PROPERTY_CUSTOM5 + 1), 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); @@ -683,11 +686,13 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_job_set_parameter( job, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast(QDMI_JOB_PARAMETER_CUSTOM5 + 1), 0, nullptr), QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_job_set_parameter( job, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast(QDMI_JOB_PARAMETER_MAX + 1), 0, nullptr), QDMI_ERROR_INVALIDARGUMENT); @@ -727,12 +732,15 @@ TEST_P(QDMIImplementationTest, JobLifecycle) { EXPECT_EQ(shots, 5); EXPECT_EQ(QDMI_job_query_property( job, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast(QDMI_JOB_PROPERTY_CUSTOM5 + 1), 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_job_query_property( - job, static_cast(QDMI_JOB_PROPERTY_MAX + 1), - 0, nullptr, nullptr), + job, + // NOLINTNEXTLINE(clang-analyzer-*) + static_cast(QDMI_JOB_PROPERTY_MAX + 1), 0, + nullptr, nullptr), QDMI_ERROR_INVALIDARGUMENT); // Queue position is optional and is not supported by the example device. EXPECT_EQ(QDMI_job_query_property(job, QDMI_JOB_PROPERTY_QUEUEPOSITION, 0, @@ -886,8 +894,10 @@ TEST_P(QDMIImplementationTest, GetResultsCornerCases) { QDMI_job_get_results(job, QDMI_JOB_RESULT_CUSTOM5, 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_job_get_results( - job, static_cast(QDMI_JOB_RESULT_CUSTOM5 + 1), - 0, nullptr, nullptr), + job, + // NOLINTNEXTLINE(clang-analyzer-*) + static_cast(QDMI_JOB_RESULT_CUSTOM5 + 1), 0, + nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_PROGRAMOUTPUT, 0, nullptr, nullptr), @@ -1335,11 +1345,13 @@ TEST_P(QDMIImplementationTest, SessionSetParameter) { QDMI_Session session2 = nullptr; ASSERT_EQ(QDMI_session_alloc(&session2), QDMI_SUCCESS); EXPECT_EQ(QDMI_session_set_parameter(session2, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast( QDMI_SESSION_PARAMETER_CUSTOM5 + 1), 0, nullptr), QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ(QDMI_session_set_parameter(session2, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast( QDMI_SESSION_PARAMETER_MAX + 1), 0, nullptr), @@ -1417,12 +1429,14 @@ TEST_P(QDMIImplementationTest, SessionQuerySessionProperty) { EXPECT_EQ( QDMI_session_query_session_property( session, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast(QDMI_SESSION_PROPERTY_CUSTOM5 + 1), 0, nullptr, nullptr), QDMI_ERROR_NOTSUPPORTED); EXPECT_EQ( QDMI_session_query_session_property( session, + // NOLINTNEXTLINE(clang-analyzer-*) static_cast(QDMI_SESSION_PROPERTY_MAX + 1), 0, nullptr, nullptr), QDMI_ERROR_INVALIDARGUMENT);