Skip to content

Commit 0e6a4e9

Browse files
✨ Add typed custom property and result queries to the C++ and Python FoMaC libraries (backport #1895) (#1896)
## Description This PR allows querying custom QDMI properties through MQT Core's C++ and Python FoMaC implementations. This allows to read any custom information exported by a device from C++ and Python via typed accessors. Assisted-by gpt-5.6-sol via Codex CLI ## Checklist - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [x] I have added migration instructions to the upgrade guide (if needed). - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] I have disclosed the use of AI tools in the PR description as per our [AI Usage Guidelines](https://github.com/munich-quantum-toolkit/core/blob/main/docs/ai_usage.md). - [x] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. (cherry picked from commit 2c9971e) Co-authored-by: Lukas Burgholzer <burgholzer@me.com>
1 parent c340ee3 commit 0e6a4e9

11 files changed

Lines changed: 920 additions & 2 deletions

File tree

.license-tools-config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"uv\\.lock",
3636
"py\\.typed",
3737
".*build.*",
38-
"(^|/)LICENSE$"
38+
"(^|/)LICENSE$",
39+
"patterns.txt"
3940
]
4041
}

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ releases may include breaking changes.
1212

1313
### Added
1414

15+
- ✨ Add typed custom property and result queries to the C++ and Python FoMaC
16+
libraries ([#1895]) ([**@burgholzer**])
1517
- ✨ Add support for custom job parameters to C++ and Python FoMaC library
1618
([#1887]) ([**@flowerthrower**], [**@burgholzer**])
1719

@@ -570,6 +572,7 @@ changelogs._
570572

571573
<!-- PR links -->
572574

575+
[#1895]: https://github.com/munich-quantum-toolkit/core/pull/1895
573576
[#1887]: https://github.com/munich-quantum-toolkit/core/pull/1887
574577
[#1873]: https://github.com/munich-quantum-toolkit/core/pull/1873
575578
[#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849

bindings/fomac/fomac.cpp

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <nanobind/stl/vector.h> // NOLINT(misc-include-cleaner)
2424
#include <qdmi/client.h>
2525

26+
#include <cstddef>
2627
#include <optional>
2728
#include <string>
2829
#include <utility>
@@ -33,6 +34,44 @@ namespace mqt {
3334
namespace nb = nanobind;
3435
using namespace nb::literals;
3536

37+
namespace {
38+
template <typename Query>
39+
[[nodiscard]] nb::object queryCustomValue(Query query,
40+
const nb::handle valueType) {
41+
const auto returnValue =
42+
[]<typename T>(std::optional<T> value) -> nb::object {
43+
if (!value.has_value()) {
44+
return nb::none();
45+
}
46+
return nb::cast(std::move(*value));
47+
};
48+
49+
const auto builtins = nb::builtins();
50+
if (valueType.is(builtins["str"])) {
51+
return returnValue(query.template operator()<std::string>());
52+
}
53+
if (valueType.is(builtins["bool"])) {
54+
return returnValue(query.template operator()<bool>());
55+
}
56+
if (valueType.is(builtins["int"])) {
57+
return returnValue(query.template operator()<int>());
58+
}
59+
if (valueType.is(builtins["float"])) {
60+
return returnValue(query.template operator()<double>());
61+
}
62+
if (valueType.is(builtins["bytes"])) {
63+
const auto value = query.template operator()<std::vector<std::byte>>();
64+
if (!value.has_value()) {
65+
return nb::none();
66+
}
67+
return nb::bytes(reinterpret_cast<const char*>(value->data()),
68+
value->size());
69+
}
70+
throw nb::type_error(
71+
"value_type must be exactly str, bool, int, float, or bytes");
72+
}
73+
} // namespace
74+
3675
NB_MODULE(MQT_CORE_MODULE_NAME, m) {
3776
// Session class
3877
auto session = nb::class_<fomac::Session>(
@@ -157,6 +196,47 @@ All authentication parameters are optional and can be provided as keyword argume
157196
"Returns the sparse probabilities from the job (typically only "
158197
"available from simulator devices).");
159198

199+
job.def(
200+
"query_custom_property",
201+
[](const fomac::Job& self, const fomac::CustomProperty customProperty,
202+
const nb::handle valueType) {
203+
return queryCustomValue(
204+
[&self, customProperty]<fomac::custom_property_value T>() {
205+
return self.queryCustomProperty<T>(customProperty);
206+
},
207+
valueType);
208+
},
209+
"custom_property"_a, "value_type"_a,
210+
nb::sig("def query_custom_property(self, custom_property: "
211+
"CustomProperty, "
212+
"value_type: type[str] | type[bool] | type[int] | type[float] | "
213+
"type[bytes]) -> str | bool | int | float | bytes | None"),
214+
R"pb(Query an implementation-defined custom job property.
215+
216+
The caller must provide the type documented by the device implementation.
217+
Use ``bytes`` to retrieve the value without interpretation. Returns ``None``
218+
when the custom slot is unsupported.)pb");
219+
220+
job.def(
221+
"get_custom_result",
222+
[](const fomac::Job& self, const fomac::CustomProperty customProperty,
223+
const nb::handle valueType) {
224+
return queryCustomValue(
225+
[&self, customProperty]<fomac::custom_property_value T>() {
226+
return self.getCustomResult<T>(customProperty);
227+
},
228+
valueType);
229+
},
230+
"custom_property"_a, "value_type"_a,
231+
nb::sig("def get_custom_result(self, custom_property: CustomProperty, "
232+
"value_type: type[str] | type[bool] | type[int] | type[float] | "
233+
"type[bytes]) -> str | bool | int | float | bytes | None"),
234+
R"pb(Return an implementation-defined custom job result.
235+
236+
The caller must provide the type documented by the device implementation.
237+
Use ``bytes`` to retrieve the value without interpretation. Returns ``None``
238+
when the custom slot is unsupported.)pb");
239+
160240
job.def_prop_ro("id", &fomac::Job::getId, "The job ID.");
161241

162242
job.def_prop_ro("program_format", &fomac::Job::getProgramFormat,
@@ -200,6 +280,15 @@ All authentication parameters are optional and can be provided as keyword argume
200280
.value("CUSTOM4", QDMI_PROGRAM_FORMAT_CUSTOM4)
201281
.value("CUSTOM5", QDMI_PROGRAM_FORMAT_CUSTOM5);
202282

283+
nb::enum_<fomac::CustomProperty>(
284+
m, "CustomProperty",
285+
"An implementation-defined custom property or result slot.")
286+
.value("CUSTOM1", fomac::CustomProperty::Custom1)
287+
.value("CUSTOM2", fomac::CustomProperty::Custom2)
288+
.value("CUSTOM3", fomac::CustomProperty::Custom3)
289+
.value("CUSTOM4", fomac::CustomProperty::Custom4)
290+
.value("CUSTOM5", fomac::CustomProperty::Custom5);
291+
203292
// Device class
204293
auto device = nb::class_<fomac::Device>(
205294
m, "Device",
@@ -270,6 +359,27 @@ All authentication parameters are optional and can be provided as keyword argume
270359
&fomac::Device::getSupportedProgramFormats,
271360
"Returns the list of program formats supported by the device.");
272361

362+
device.def(
363+
"query_custom_property",
364+
[](const fomac::Device& self, const fomac::CustomProperty customProperty,
365+
const nb::handle valueType) {
366+
return queryCustomValue(
367+
[&self, customProperty]<fomac::custom_property_value T>() {
368+
return self.queryCustomProperty<T>(customProperty);
369+
},
370+
valueType);
371+
},
372+
"custom_property"_a, "value_type"_a,
373+
nb::sig("def query_custom_property(self, custom_property: "
374+
"CustomProperty, "
375+
"value_type: type[str] | type[bool] | type[int] | type[float] | "
376+
"type[bytes]) -> str | bool | int | float | bytes | None"),
377+
R"pb(Query an implementation-defined custom device property.
378+
379+
The caller must provide the type documented by the device implementation.
380+
Use ``bytes`` to retrieve the value without interpretation. Returns ``None``
381+
when the custom slot is unsupported.)pb");
382+
273383
device.def("submit_job", &fomac::Device::submitJob, "program"_a,
274384
"program_format"_a, "num_shots"_a, nb::kw_only(),
275385
"custom1"_a = nb::none(), "custom2"_a = nb::none(),
@@ -328,6 +438,27 @@ All authentication parameters are optional and can be provided as keyword argume
328438
site.def("submodule_index", &fomac::Site::getSubmoduleIndex,
329439
"Returns the index of the submodule the site belongs to.");
330440

441+
site.def(
442+
"query_custom_property",
443+
[](const fomac::Site& self, const fomac::CustomProperty customProperty,
444+
const nb::handle valueType) {
445+
return queryCustomValue(
446+
[&self, customProperty]<fomac::custom_property_value T>() {
447+
return self.queryCustomProperty<T>(customProperty);
448+
},
449+
valueType);
450+
},
451+
"custom_property"_a, "value_type"_a,
452+
nb::sig("def query_custom_property(self, custom_property: "
453+
"CustomProperty, "
454+
"value_type: type[str] | type[bool] | type[int] | type[float] | "
455+
"type[bytes]) -> str | bool | int | float | bytes | None"),
456+
R"pb(Query an implementation-defined custom site property.
457+
458+
The caller must provide the type documented by the device implementation.
459+
Use ``bytes`` to retrieve the value without interpretation. Returns ``None``
460+
when the custom slot is unsupported.)pb");
461+
331462
site.def("__repr__", [](const fomac::Site& s) {
332463
return "<Site index=" + std::to_string(s.getIndex()) + ">";
333464
});
@@ -399,6 +530,34 @@ All authentication parameters are optional and can be provided as keyword argume
399530
"params"_a.sig("...") = std::vector<double>{},
400531
"Returns the mean shuttling speed of the operation.");
401532

533+
operation.def(
534+
"query_custom_property",
535+
[](const fomac::Operation& self,
536+
const fomac::CustomProperty customProperty, const nb::handle valueType,
537+
const std::vector<fomac::Site>& sites,
538+
const std::vector<double>& params) {
539+
return queryCustomValue(
540+
[&self, customProperty, &sites,
541+
&params]<fomac::custom_property_value T>() {
542+
return self.queryCustomProperty<T>(customProperty, sites, params);
543+
},
544+
valueType);
545+
},
546+
"custom_property"_a, "value_type"_a,
547+
"sites"_a.sig("...") = std::vector<fomac::Site>{},
548+
"params"_a.sig("...") = std::vector<double>{},
549+
nb::sig("def query_custom_property(self, custom_property: "
550+
"CustomProperty, "
551+
"value_type: type[str] | type[bool] | type[int] | type[float] | "
552+
"type[bytes], sites: Sequence[mqt.core.fomac.Device.Site] = "
553+
"..., params: Sequence[float] = ...) -> str | bool | int | "
554+
"float | bytes | None"),
555+
R"pb(Query an implementation-defined custom operation property.
556+
557+
The caller must provide the type documented by the device implementation.
558+
Use ``bytes`` to retrieve the value without interpretation. Returns ``None``
559+
when the custom slot is unsupported.)pb");
560+
402561
operation.def("__repr__", [](const fomac::Operation& op) {
403562
return "<Operation name=\"" + op.getName() + "\">";
404563
});

bindings/fomac/patterns.txt

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
mqt\.core\.fomac\.(?:Job|Device|Device\.Site)\.query_custom_property$:
2+
\from typing import overload
3+
@overload
4+
def query_custom_property(self, custom_property: CustomProperty, value_type: type[str]) -> str | None: ...
5+
@overload
6+
def query_custom_property(self, custom_property: CustomProperty, value_type: type[bool]) -> bool | None: ...
7+
@overload
8+
def query_custom_property(self, custom_property: CustomProperty, value_type: type[int]) -> int | None: ...
9+
@overload
10+
def query_custom_property(self, custom_property: CustomProperty, value_type: type[float]) -> float | None: ...
11+
@overload
12+
def query_custom_property(self, custom_property: CustomProperty, value_type: type[bytes]) -> bytes | None: ...
13+
@overload
14+
def query_custom_property(
15+
self, custom_property: CustomProperty, value_type: type[str | bool | int | float | bytes]
16+
) -> str | bool | int | float | bytes | None:
17+
\doc
18+
19+
mqt\.core\.fomac\.Job\.get_custom_result$:
20+
\from typing import overload
21+
@overload
22+
def get_custom_result(self, custom_property: CustomProperty, value_type: type[str]) -> str | None: ...
23+
@overload
24+
def get_custom_result(self, custom_property: CustomProperty, value_type: type[bool]) -> bool | None: ...
25+
@overload
26+
def get_custom_result(self, custom_property: CustomProperty, value_type: type[int]) -> int | None: ...
27+
@overload
28+
def get_custom_result(self, custom_property: CustomProperty, value_type: type[float]) -> float | None: ...
29+
@overload
30+
def get_custom_result(self, custom_property: CustomProperty, value_type: type[bytes]) -> bytes | None: ...
31+
@overload
32+
def get_custom_result(
33+
self, custom_property: CustomProperty, value_type: type[str | bool | int | float | bytes]
34+
) -> str | bool | int | float | bytes | None:
35+
\doc
36+
37+
mqt\.core\.fomac\.Device\.Operation\.query_custom_property$:
38+
\from typing import overload
39+
@overload
40+
def query_custom_property(
41+
self,
42+
custom_property: CustomProperty,
43+
value_type: type[str],
44+
sites: Sequence[Device.Site] = ...,
45+
params: Sequence[float] = ...,
46+
) -> str | None: ...
47+
@overload
48+
def query_custom_property(
49+
self,
50+
custom_property: CustomProperty,
51+
value_type: type[bool],
52+
sites: Sequence[Device.Site] = ...,
53+
params: Sequence[float] = ...,
54+
) -> bool | None: ...
55+
@overload
56+
def query_custom_property(
57+
self,
58+
custom_property: CustomProperty,
59+
value_type: type[int],
60+
sites: Sequence[Device.Site] = ...,
61+
params: Sequence[float] = ...,
62+
) -> int | None: ...
63+
@overload
64+
def query_custom_property(
65+
self,
66+
custom_property: CustomProperty,
67+
value_type: type[float],
68+
sites: Sequence[Device.Site] = ...,
69+
params: Sequence[float] = ...,
70+
) -> float | None: ...
71+
@overload
72+
def query_custom_property(
73+
self,
74+
custom_property: CustomProperty,
75+
value_type: type[bytes],
76+
sites: Sequence[Device.Site] = ...,
77+
params: Sequence[float] = ...,
78+
) -> bytes | None: ...
79+
@overload
80+
def query_custom_property(
81+
self,
82+
custom_property: CustomProperty,
83+
value_type: type[str | bool | int | float | bytes],
84+
sites: Sequence[Device.Site] = ...,
85+
params: Sequence[float] = ...,
86+
) -> str | bool | int | float | bytes | None:
87+
\doc

0 commit comments

Comments
 (0)