Skip to content

Commit c340ee3

Browse files
mergify[bot]flowerthrowerburgholzer
authored
✨ Add custom job parameters to FoMaC (backport #1887) (#1891)
## Description This PR adds the custom1-5 job parameters to FoMaC. During implementing the [CUDAQ QDMI Integration](NVIDIA/cuda-quantum#4882), I found that currently FoMaC only exposes custom session parameters but not custom job parameters (which is, e.g., crucial for using the [Amazon Braket QDMI Device](https://github.com/munich-quantum-software/amazon-braket-qdmi-device)). ## Checklist - [ ] The pull request only contains commits that are focused and relevant to this change. - [ ] I have added appropriate tests that cover the new/changed functionality. - [ ] I have updated the documentation to reflect these changes. - [ ] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [ ] I have added migration instructions to the upgrade guide (if needed). - [ ] The changes follow the project's style guidelines and introduce no new warnings. - [ ] The changes are fully tested and pass the CI checks. - [ ] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [ ] 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). - [ ] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [ ] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. (cherry picked from commit df90a5b) --------- Co-authored-by: Patrick Hopf <81010725+flowerthrower@users.noreply.github.com> Co-authored-by: Lukas Burgholzer <burgholzer@me.com>
1 parent 790e814 commit c340ee3

9 files changed

Lines changed: 167 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ releases may include breaking changes.
1010

1111
## [Unreleased]
1212

13+
### Added
14+
15+
- ✨ Add support for custom job parameters to C++ and Python FoMaC library
16+
([#1887]) ([**@flowerthrower**], [**@burgholzer**])
17+
1318
## [3.7.0] - 2026-07-09
1419

1520
_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#370)._
@@ -565,6 +570,7 @@ changelogs._
565570

566571
<!-- PR links -->
567572

573+
[#1887]: https://github.com/munich-quantum-toolkit/core/pull/1887
568574
[#1873]: https://github.com/munich-quantum-toolkit/core/pull/1873
569575
[#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849
570576
[#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848

bindings/fomac/fomac.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <nanobind/stl/optional.h> // NOLINT(misc-include-cleaner)
2020
#include <nanobind/stl/pair.h> // NOLINT(misc-include-cleaner)
2121
#include <nanobind/stl/string.h> // NOLINT(misc-include-cleaner)
22+
#include <nanobind/stl/variant.h> // NOLINT(misc-include-cleaner)
2223
#include <nanobind/stl/vector.h> // NOLINT(misc-include-cleaner)
2324
#include <qdmi/client.h>
2425

@@ -270,8 +271,11 @@ All authentication parameters are optional and can be provided as keyword argume
270271
"Returns the list of program formats supported by the device.");
271272

272273
device.def("submit_job", &fomac::Device::submitJob, "program"_a,
273-
"program_format"_a, "num_shots"_a,
274-
nb::rv_policy::reference_internal, "Submits a job to the device.");
274+
"program_format"_a, "num_shots"_a, nb::kw_only(),
275+
"custom1"_a = nb::none(), "custom2"_a = nb::none(),
276+
"custom3"_a = nb::none(), "custom4"_a = nb::none(),
277+
"custom5"_a = nb::none(), nb::rv_policy::reference_internal,
278+
"Submits a job to the device.");
275279

276280
device.def("__repr__", [](const fomac::Device& dev) {
277281
return "<Device name=\"" + dev.getName() + "\">";

include/mqt-core/fomac/FoMaC.hpp

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@
2828
#include <string>
2929
#include <type_traits>
3030
#include <utility>
31+
#include <variant>
3132
#include <vector>
3233

3334
namespace fomac {
35+
using CustomJobParameter = std::variant<std::string, bool, int, double>;
36+
3437
/**
3538
* @brief Concept for ranges that are contiguous in memory and can be
3639
* constructed with a size.
@@ -105,8 +108,7 @@ template <typename U> struct remove_optional<std::optional<U>> {
105108
* with the underlying type of optional without caring about its optionality.
106109
* @tparam T The type to strip optional from.
107110
*/
108-
template <typename T>
109-
using remove_optional_t = typename remove_optional<T>::type;
111+
template <typename T> using remove_optional_t = remove_optional<T>::type;
110112

111113
/**
112114
* @brief Concept for types that are either size_constructible_contiguous_range
@@ -211,7 +213,7 @@ class Session {
211213
/// Query a session property.
212214
template <size_constructible_contiguous_range T>
213215
[[nodiscard]] T queryProperty(const QDMI_Session_Property prop) const {
214-
using StrippedValueType = typename remove_optional_t<T>::value_type;
216+
using StrippedValueType = remove_optional_t<T>::value_type;
215217

216218
size_t size = 0;
217219
qdmi::throwIfError(QDMI_session_query_session_property(session_.get(), prop,
@@ -318,9 +320,13 @@ class Device {
318320
getSupportedProgramFormats() const;
319321

320322
/// @see QDMI_job_submit
321-
[[nodiscard]] Job submitJob(const std::string& program,
322-
QDMI_Program_Format format,
323-
size_t numShots) const;
323+
[[nodiscard]] Job submitJob(
324+
const std::string& program, QDMI_Program_Format format, size_t numShots,
325+
const std::optional<CustomJobParameter>& custom1 = std::nullopt,
326+
const std::optional<CustomJobParameter>& custom2 = std::nullopt,
327+
const std::optional<CustomJobParameter>& custom3 = std::nullopt,
328+
const std::optional<CustomJobParameter>& custom4 = std::nullopt,
329+
const std::optional<CustomJobParameter>& custom5 = std::nullopt) const;
324330

325331
auto operator<=>(const Device&) const noexcept = default;
326332

@@ -389,6 +395,9 @@ class Device {
389395
}
390396
}
391397

398+
static void setCustomJobParam(QDMI_Job job, QDMI_Job_Parameter param,
399+
const CustomJobParameter& value);
400+
392401
/// @brief The underlying device pointer.
393402
QDMI_Device device_;
394403

@@ -566,8 +575,8 @@ class Site {
566575
[[nodiscard]] T queryProperty(const QDMI_Site_Property prop) const {
567576
if constexpr (string_or_optional_string<T>) {
568577
size_t size = 0;
569-
auto result = QDMI_device_query_site_property(*device_, site_, prop, 0,
570-
nullptr, &size);
578+
const auto result = QDMI_device_query_site_property(*device_, site_, prop,
579+
0, nullptr, &size);
571580
if constexpr (is_optional<T>) {
572581
if (result == QDMI_ERROR_NOTSUPPORTED) {
573582
return std::nullopt;
@@ -576,8 +585,6 @@ class Site {
576585
qdmi::throwIfError(result,
577586
std::string("Querying size") + qdmi::toString(prop));
578587
std::string value(size - 1, '\0');
579-
result = QDMI_device_query_site_property(*device_, site_, prop, size,
580-
value.data(), nullptr);
581588
qdmi::throwIfError(QDMI_device_query_site_property(*device_, site_, prop,
582589
size, value.data(),
583590
nullptr),

python/mqt/core/fomac.pyi

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,18 @@ class Device:
251251
def supported_program_formats(self) -> list[ProgramFormat]:
252252
"""Returns the list of program formats supported by the device."""
253253

254-
def submit_job(self, program: str, program_format: ProgramFormat, num_shots: int) -> Job:
254+
def submit_job(
255+
self,
256+
program: str,
257+
program_format: ProgramFormat,
258+
num_shots: int,
259+
*,
260+
custom1: str | bool | float | None = None,
261+
custom2: str | bool | float | None = None,
262+
custom3: str | bool | float | None = None,
263+
custom4: str | bool | float | None = None,
264+
custom5: str | bool | float | None = None,
265+
) -> Job:
255266
"""Submits a job to the device."""
256267

257268
def __eq__(self, arg: object, /) -> bool: ...

src/fomac/FoMaC.cpp

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
#include <sstream>
2929
#include <stdexcept>
3030
#include <string>
31+
#include <type_traits>
3132
#include <utility>
33+
#include <variant>
3234
#include <vector>
3335

3436
namespace fomac {
@@ -285,35 +287,70 @@ std::vector<QDMI_Program_Format> Device::getSupportedProgramFormats() const {
285287
}
286288

287289
Job Device::submitJob(const std::string& program,
288-
const QDMI_Program_Format format,
289-
const size_t numShots) const {
290+
const QDMI_Program_Format format, const size_t numShots,
291+
const std::optional<CustomJobParameter>& custom1,
292+
const std::optional<CustomJobParameter>& custom2,
293+
const std::optional<CustomJobParameter>& custom3,
294+
const std::optional<CustomJobParameter>& custom4,
295+
const std::optional<CustomJobParameter>& custom5) const {
290296
QDMI_Job job = nullptr;
291297
qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job");
292-
Job jobWrapper{job}; // RAII wrapper to prevent leaks in case of exceptions
298+
Job jobWrapper{job};
293299

294-
// Set program format
295300
qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper,
296301
QDMI_JOB_PARAMETER_PROGRAMFORMAT,
297302
sizeof(format), &format),
298303
"Setting program format");
299-
300-
// Set program
301304
qdmi::throwIfError(
302305
QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM,
303306
program.size() + 1, program.c_str()),
304307
"Setting program");
305-
306-
// Set number of shots
307308
qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper,
308309
QDMI_JOB_PARAMETER_SHOTSNUM,
309310
sizeof(numShots), &numShots),
310311
"Setting number of shots");
311312

312-
// Submit the job
313+
if (custom1.has_value()) {
314+
setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM1, *custom1);
315+
}
316+
if (custom2.has_value()) {
317+
setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM2, *custom2);
318+
}
319+
if (custom3.has_value()) {
320+
setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM3, *custom3);
321+
}
322+
if (custom4.has_value()) {
323+
setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM4, *custom4);
324+
}
325+
if (custom5.has_value()) {
326+
setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM5, *custom5);
327+
}
328+
313329
qdmi::throwIfError(QDMI_job_submit(jobWrapper), "Submitting job");
314330
return jobWrapper;
315331
}
316332

333+
void Device::setCustomJobParam(QDMI_Job job, const QDMI_Job_Parameter param,
334+
const CustomJobParameter& value) {
335+
std::visit(
336+
[&]<typename CustomValue>(const CustomValue& customValue) {
337+
using T = std::decay_t<CustomValue>;
338+
if constexpr (std::is_same_v<T, std::string>) {
339+
qdmi::throwIfError(QDMI_job_set_parameter(job, param,
340+
customValue.size() + 1,
341+
customValue.c_str()),
342+
"Setting custom parameter");
343+
} else {
344+
static_assert(std::is_trivially_copyable_v<T>,
345+
"Custom job parameters must be trivially copyable");
346+
qdmi::throwIfError(
347+
QDMI_job_set_parameter(job, param, sizeof(T), &customValue),
348+
"Setting custom parameter");
349+
}
350+
},
351+
value);
352+
}
353+
317354
QDMI_Job_Status Job::check() const {
318355
QDMI_Job_Status status{};
319356
qdmi::throwIfError(QDMI_job_check(job_.get(), &status),

src/qdmi/driver/Driver.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,16 @@ namespace {
275275
return QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT;
276276
case QDMI_JOB_PARAMETER_SHOTSNUM:
277277
return QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM;
278+
case QDMI_JOB_PARAMETER_CUSTOM1:
279+
return QDMI_DEVICE_JOB_PARAMETER_CUSTOM1;
280+
case QDMI_JOB_PARAMETER_CUSTOM2:
281+
return QDMI_DEVICE_JOB_PARAMETER_CUSTOM2;
282+
case QDMI_JOB_PARAMETER_CUSTOM3:
283+
return QDMI_DEVICE_JOB_PARAMETER_CUSTOM3;
284+
case QDMI_JOB_PARAMETER_CUSTOM4:
285+
return QDMI_DEVICE_JOB_PARAMETER_CUSTOM4;
286+
case QDMI_JOB_PARAMETER_CUSTOM5:
287+
return QDMI_DEVICE_JOB_PARAMETER_CUSTOM5;
278288
default:
279289
return QDMI_DEVICE_JOB_PARAMETER_MAX;
280290
}
@@ -286,7 +296,8 @@ QDMI_Job_impl_d::~QDMI_Job_impl_d() {
286296
}
287297
auto QDMI_Job_impl_d::setParameter(QDMI_Job_Parameter param, const size_t size,
288298
const void* value) const -> int {
289-
if ((value != nullptr && size == 0) || param >= QDMI_JOB_PARAMETER_MAX) {
299+
if ((value != nullptr && size == 0) ||
300+
IS_INVALID_ARGUMENT(param, QDMI_JOB_PARAMETER)) {
290301
return QDMI_ERROR_INVALIDARGUMENT;
291302
}
292303
return device_->getLibrary().device_job_set_parameter(

test/fomac/test_fomac.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <fstream>
2222
#include <new>
2323
#include <numbers>
24+
#include <optional>
2425
#include <ranges>
2526
#include <stdexcept>
2627
#include <string>
@@ -556,6 +557,51 @@ c = measure q;)";
556557
EXPECT_EQ(job.check(), QDMI_JOB_STATUS_DONE);
557558
}
558559

560+
TEST_F(DDSimulatorDeviceTest, SubmitJobCustomSupportedTypes) {
561+
constexpr auto qasm3Program = "OPENQASM 3.0;";
562+
563+
auto submitWithCustoms = [&](auto custom, const size_t which) {
564+
try {
565+
switch (which) {
566+
case 1:
567+
std::ignore = device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3,
568+
10, custom);
569+
break;
570+
case 2:
571+
std::ignore = device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3,
572+
10, std::nullopt, custom);
573+
break;
574+
case 3:
575+
std::ignore = device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3,
576+
10, std::nullopt, std::nullopt, custom);
577+
break;
578+
case 4:
579+
std::ignore =
580+
device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10,
581+
std::nullopt, std::nullopt, std::nullopt, custom);
582+
break;
583+
case 5:
584+
std::ignore = device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3,
585+
10, std::nullopt, std::nullopt,
586+
std::nullopt, std::nullopt, custom);
587+
break;
588+
default:
589+
throw std::invalid_argument("Invalid 'which' value");
590+
}
591+
} catch (const std::runtime_error& e) {
592+
const std::string errorMsg(e.what());
593+
EXPECT_TRUE(errorMsg.find("Setting custom parameter") !=
594+
std::string::npos);
595+
}
596+
};
597+
for (size_t i = 1; i <= 5; ++i) {
598+
submitWithCustoms(std::string("custom"), i);
599+
submitWithCustoms(42, i);
600+
submitWithCustoms(3.14, i);
601+
submitWithCustoms(true, i);
602+
}
603+
}
604+
559605
TEST_F(DDSimulatorDeviceTest, SubmitJobPreservesNumShots) {
560606
const std::string qasm3Program = R"(
561607
OPENQASM 3.0;

test/python/fomac/test_fomac.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,20 @@ def test_device_submit_job_returns_valid_job(ddsim_device: Device) -> None:
421421
assert job.num_shots == 100
422422

423423

424+
def test_device_submit_job_handles_custom_parameters(ddsim_device: Device) -> None:
425+
"""Test that submit_job forwards custom job parameters to DDSIM."""
426+
with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."):
427+
ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom1="value")
428+
with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."):
429+
ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom2="value")
430+
with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."):
431+
ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom3="value")
432+
with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."):
433+
ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom4="value")
434+
with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."):
435+
ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom5="value")
436+
437+
424438
def test_device_submit_job_preserves_num_shots(ddsim_device: Device) -> None:
425439
"""Test that different shot counts are correctly preserved."""
426440
qasm3_program = """

test/qdmi/driver/test_driver.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ TEST_P(DriverJobTest, JobSetParameter) {
168168
EXPECT_THAT(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_SHOTSNUM,
169169
sizeof(size_t), &numShots),
170170
testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED));
171+
constexpr std::array customParams{
172+
QDMI_JOB_PARAMETER_CUSTOM1, QDMI_JOB_PARAMETER_CUSTOM2,
173+
QDMI_JOB_PARAMETER_CUSTOM3, QDMI_JOB_PARAMETER_CUSTOM4,
174+
QDMI_JOB_PARAMETER_CUSTOM5};
175+
for (const auto param : customParams) {
176+
EXPECT_THAT(QDMI_job_set_parameter(job, param, 0, nullptr),
177+
testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED));
178+
}
171179
EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_MAX, 0, nullptr),
172180
QDMI_ERROR_INVALIDARGUMENT);
173181
}

0 commit comments

Comments
 (0)