From 402c0533b3a9db2a05ae789704d087c31efacac6 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Thu, 9 Jul 2026 14:26:29 +0200 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=9A=A7=20add=20custom=20job=20param?= =?UTF-8?q?eters=20to=20cpp=20fomac?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/qdmi/driver/Driver.cpp | 13 ++++++++++++- test/qdmi/driver/test_driver.cpp | 10 ++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/qdmi/driver/Driver.cpp b/src/qdmi/driver/Driver.cpp index c2c1dba036..18b85434c7 100644 --- a/src/qdmi/driver/Driver.cpp +++ b/src/qdmi/driver/Driver.cpp @@ -275,6 +275,16 @@ namespace { return QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT; case QDMI_JOB_PARAMETER_SHOTSNUM: return QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM; + case QDMI_JOB_PARAMETER_CUSTOM1: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM1; + case QDMI_JOB_PARAMETER_CUSTOM2: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM2; + case QDMI_JOB_PARAMETER_CUSTOM3: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM3; + case QDMI_JOB_PARAMETER_CUSTOM4: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM4; + case QDMI_JOB_PARAMETER_CUSTOM5: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM5; default: return QDMI_DEVICE_JOB_PARAMETER_MAX; } @@ -286,7 +296,8 @@ QDMI_Job_impl_d::~QDMI_Job_impl_d() { } auto QDMI_Job_impl_d::setParameter(QDMI_Job_Parameter param, const size_t size, const void* value) const -> int { - if ((value != nullptr && size == 0) || param >= QDMI_JOB_PARAMETER_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(param, QDMI_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } return device_->getLibrary().device_job_set_parameter( diff --git a/test/qdmi/driver/test_driver.cpp b/test/qdmi/driver/test_driver.cpp index 42b876e5c1..563f7a2d5e 100644 --- a/test/qdmi/driver/test_driver.cpp +++ b/test/qdmi/driver/test_driver.cpp @@ -168,6 +168,16 @@ TEST_P(DriverJobTest, JobSetParameter) { EXPECT_THAT(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_SHOTSNUM, sizeof(size_t), &numShots), testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + constexpr std::array customParams{ + QDMI_JOB_PARAMETER_CUSTOM1, QDMI_JOB_PARAMETER_CUSTOM2, + QDMI_JOB_PARAMETER_CUSTOM3, QDMI_JOB_PARAMETER_CUSTOM4, + QDMI_JOB_PARAMETER_CUSTOM5}; + constexpr std::array customValue{std::byte{42}}; + for (const auto param : customParams) { + EXPECT_THAT(QDMI_job_set_parameter(job, param, customValue.size(), + customValue.data()), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + } EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_MAX, 0, nullptr), QDMI_ERROR_INVALIDARGUMENT); } From 7f52260b45c830279a4ed6265953f64c54bdd9dd Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Thu, 9 Jul 2026 16:09:37 +0200 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=9A=A7=20add=20optional=20custom=20?= =?UTF-8?q?job=20parameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/fomac/fomac.cpp | 20 +++++++++++++--- include/mqt-core/fomac/FoMaC.hpp | 8 +++++-- python/mqt/core/fomac.pyi | 13 ++++++++++- src/fomac/FoMaC.cpp | 40 ++++++++++++++++++++++++++++++-- test/fomac/test_fomac.cpp | 19 +++++++++++++++ test/python/fomac/test_fomac.py | 14 +++++++++++ 6 files changed, 106 insertions(+), 8 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index cf64468e41..2a6595a969 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -269,9 +269,23 @@ All authentication parameters are optional and can be provided as keyword argume &fomac::Device::getSupportedProgramFormats, "Returns the list of program formats supported by the device."); - device.def("submit_job", &fomac::Device::submitJob, "program"_a, - "program_format"_a, "num_shots"_a, - nb::rv_policy::reference_internal, "Submits a job to the device."); + device.def( + "submit_job", + [](const fomac::Device& self, const std::string& program, + const QDMI_Program_Format programFormat, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) { + return self.submitJob(program, programFormat, numShots, custom1, + custom2, custom3, custom4, custom5); + }, + "program"_a, "program_format"_a, "num_shots"_a, nb::kw_only(), + "custom1"_a = std::nullopt, "custom2"_a = std::nullopt, + "custom3"_a = std::nullopt, "custom4"_a = std::nullopt, + "custom5"_a = std::nullopt, nb::rv_policy::reference_internal, + "Submits a job to the device."); device.def("__repr__", [](const fomac::Device& dev) { return ""; diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index d34077ae43..4a2ff9605a 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -319,8 +319,12 @@ class Device { /// @see QDMI_job_submit [[nodiscard]] Job submitJob(const std::string& program, - QDMI_Program_Format format, - size_t numShots) const; + QDMI_Program_Format format, size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) const; auto operator<=>(const Device&) const noexcept = default; diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 6dfef7e4db..84387483e5 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -251,7 +251,18 @@ class Device: def supported_program_formats(self) -> list[ProgramFormat]: """Returns the list of program formats supported by the device.""" - def submit_job(self, program: str, program_format: ProgramFormat, num_shots: int) -> Job: + def submit_job( + self, + program: str, + program_format: ProgramFormat, + num_shots: int, + *, + custom1: str | None = None, + custom2: str | None = None, + custom3: str | None = None, + custom4: str | None = None, + custom5: str | None = None, + ) -> Job: """Submits a job to the device.""" def __eq__(self, arg: object, /) -> bool: ... diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 299fbbf9a8..2a46a4b874 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -285,8 +285,12 @@ std::vector Device::getSupportedProgramFormats() const { } Job Device::submitJob(const std::string& program, - const QDMI_Program_Format format, - const size_t numShots) const { + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) const { QDMI_Job job = nullptr; qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); Job jobWrapper{job}; // RAII wrapper to prevent leaks in case of exceptions @@ -309,6 +313,38 @@ Job Device::submitJob(const std::string& program, sizeof(numShots), &numShots), "Setting number of shots"); + // Set custom parameters if provided + if (custom1.has_value()) { + qdmi::throwIfError( + QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM1, + custom1->size() + 1, custom1->c_str()), + "Setting custom1"); + } + if (custom2.has_value()) { + qdmi::throwIfError( + QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM2, + custom2->size() + 1, custom2->c_str()), + "Setting custom2"); + } + if (custom3.has_value()) { + qdmi::throwIfError( + QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM3, + custom3->size() + 1, custom3->c_str()), + "Setting custom3"); + } + if (custom4.has_value()) { + qdmi::throwIfError( + QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM4, + custom4->size() + 1, custom4->c_str()), + "Setting custom4"); + } + if (custom5.has_value()) { + qdmi::throwIfError( + QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM5, + custom5->size() + 1, custom5->c_str()), + "Setting custom5"); + } + // Submit the job qdmi::throwIfError(QDMI_job_submit(jobWrapper), "Submitting job"); return jobWrapper; diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 48ed1bffd3..60042318ae 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -556,6 +556,25 @@ c = measure q;)"; EXPECT_EQ(job.check(), QDMI_JOB_STATUS_DONE); } +TEST_F(DDSimulatorDeviceTest, SubmitJobAcceptsCustomParameters) { + const std::string qasm3Program = R"( +OPENQASM 3.0; +qubit[1] q; +bit[1] c; +c[0] = measure q[0]; +)"; + const std::optional custom1 = "custom_value1"; + const std::optional custom2 = "custom_value2"; + const std::optional custom3 = "custom_value3"; + const std::optional custom4 = "custom_value4"; + const std::optional custom5 = "custom_value5"; + + EXPECT_THROW(std::ignore = device.submitJob( + qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, custom1, + custom2, custom3, custom4, custom5), + std::runtime_error); +} + TEST_F(DDSimulatorDeviceTest, SubmitJobPreservesNumShots) { const std::string qasm3Program = R"( OPENQASM 3.0; diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index e53a04799b..8790efb295 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -421,6 +421,20 @@ def test_device_submit_job_returns_valid_job(ddsim_device: Device) -> None: assert job.num_shots == 100 +def test_device_submit_job_handles_custom_parameters(ddsim_device: Device) -> None: + """Test that submit_job handles custom job parameters (even though no current device uses them).""" + with pytest.raises(RuntimeError, match=r"Setting custom job parameter 1: Not supported\."): + ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom1="cstm") + with pytest.raises(RuntimeError, match=r"Setting custom job parameter 2: Not supported\."): + ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom2="cstm") + with pytest.raises(RuntimeError, match=r"Setting custom job parameter 3: Not supported\."): + ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom3="cstm") + with pytest.raises(RuntimeError, match=r"Setting custom job parameter 4: Not supported\."): + ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom4="cstm") + with pytest.raises(RuntimeError, match=r"Setting custom job parameter 5: Not supported\."): + ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom5="cstm") + + def test_device_submit_job_preserves_num_shots(ddsim_device: Device) -> None: """Test that different shot counts are correctly preserved.""" qasm3_program = """ From 4bc3166653a6eaacf38d96c30db4177a7d82ca08 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 10 Jul 2026 09:27:12 +0200 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=9A=A7=20add=20default=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/mqt-core/fomac/FoMaC.hpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 4a2ff9605a..c07b0e4ad7 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -318,13 +318,14 @@ class Device { getSupportedProgramFormats() const; /// @see QDMI_job_submit - [[nodiscard]] Job submitJob(const std::string& program, - QDMI_Program_Format format, size_t numShots, - const std::optional& custom1, - const std::optional& custom2, - const std::optional& custom3, - const std::optional& custom4, - const std::optional& custom5) const; + [[nodiscard]] Job + submitJob(const std::string& program, QDMI_Program_Format format, + size_t numShots, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) const; auto operator<=>(const Device&) const noexcept = default; From a9951c57a73442889b55706e523fefdf596e3fa2 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 10 Jul 2026 10:45:09 +0200 Subject: [PATCH 04/10] =?UTF-8?q?=F0=9F=9A=A7=20add=20QDMI=20device=20libr?= =?UTF-8?q?ary=20lookup=20on=20Unix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/qdmi/driver/Driver.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/qdmi/driver/Driver.cpp b/src/qdmi/driver/Driver.cpp index c2c1dba036..e7083b0331 100644 --- a/src/qdmi/driver/Driver.cpp +++ b/src/qdmi/driver/Driver.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -32,8 +33,6 @@ #ifdef _WIN32 #include - -#include #else #include #endif // _WIN32 @@ -93,7 +92,31 @@ namespace { reinterpret_cast(GetProcAddress(static_cast((lib)), (sym))) #define DL_CLOSE(lib) FreeLibrary(static_cast((lib))) #else -#define DL_OPEN(lib) dlopen((lib), RTLD_NOW | RTLD_LOCAL) +namespace { +/// Loads the device library with the given name, searching in the driver +/// directory if no path is specified. +[[nodiscard]] auto loadDeviceLibrary(const char* libName) -> void* { + const auto requested = std::filesystem::path(libName); + + if (requested.has_parent_path()) { + return dlopen(libName, RTLD_NOW | RTLD_LOCAL); + } + + Dl_info info{}; + if (dladdr(reinterpret_cast(&loadDeviceLibrary), &info) != 0 && + info.dli_fname != nullptr) { + const auto path = + std::filesystem::path(info.dli_fname).parent_path() / requested; + if (auto* handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + handle != nullptr) { + return handle; + } + } + return dlopen(libName, RTLD_NOW | RTLD_LOCAL); +} +} // namespace + +#define DL_OPEN(lib) loadDeviceLibrary((lib)) #define DL_SYM(lib, sym) dlsym((lib), (sym)) #define DL_CLOSE(lib) dlclose((lib)) #endif From 4beb20783f7809d1a6dde0cdbd7b0b3a565dd17c Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 10 Jul 2026 14:32:48 +0200 Subject: [PATCH 05/10] =?UTF-8?q?=F0=9F=9A=A7=20add=20cpp=20templates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/mqt-core/fomac/FoMaC.hpp | 72 +++++++++++++++++++++++++++++--- src/fomac/FoMaC.cpp | 66 ----------------------------- test/fomac/test_fomac.cpp | 34 ++++++++++----- 3 files changed, 90 insertions(+), 82 deletions(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index c07b0e4ad7..8d49e47b3d 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -317,15 +317,18 @@ class Device { [[nodiscard]] std::vector getSupportedProgramFormats() const; + template + /// @see QDMI_job_submit [[nodiscard]] Job submitJob(const std::string& program, QDMI_Program_Format format, - size_t numShots, - const std::optional& custom1 = std::nullopt, - const std::optional& custom2 = std::nullopt, - const std::optional& custom3 = std::nullopt, - const std::optional& custom4 = std::nullopt, - const std::optional& custom5 = std::nullopt) const; + size_t numShots, const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) const; auto operator<=>(const Device&) const noexcept = default; @@ -398,6 +401,28 @@ class Device { QDMI_Device device_; friend class Session; + + template + static void setCustomJobParam(QDMI_Job job, QDMI_Job_Parameter param, + const std::optional& value) { + if (!value.has_value()) { + return; + } + + if constexpr (std::is_same_v) { + qdmi::throwIfError( + QDMI_job_set_parameter(job, param, value->size() + 1, value->c_str()), + "Setting custom parameter"); + } else if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { + qdmi::throwIfError( + QDMI_job_set_parameter(job, param, sizeof(T), &value.value()), + "Setting custom parameter"); + } else { + throw std::runtime_error("Unsupported custom job parameter type, must be " + "string, int, float, or double"); + } + } }; /** @@ -503,6 +528,41 @@ static_assert(!std::is_copy_assignable()); static_assert(std::is_move_constructible()); static_assert(std::is_move_assignable()); +template +Job Device::submitJob(const std::string& program, + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) const { + QDMI_Job job = nullptr; + qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); + Job jobWrapper{job}; + + qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, + QDMI_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(format), &format), + "Setting program format"); + qdmi::throwIfError( + QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, + program.size() + 1, program.c_str()), + "Setting program"); + qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, + QDMI_JOB_PARAMETER_SHOTSNUM, + sizeof(numShots), &numShots), + "Setting number of shots"); + + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM1, custom1); + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM2, custom2); + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM3, custom3); + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM4, custom4); + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM5, custom5); + + qdmi::throwIfError(QDMI_job_submit(jobWrapper), "Submitting job"); + return jobWrapper; +} + /** * @brief Class representing a site (qubit) on the device. * @details diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 2a46a4b874..b1822f765a 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -284,72 +284,6 @@ std::vector Device::getSupportedProgramFormats() const { QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS); } -Job Device::submitJob(const std::string& program, - const QDMI_Program_Format format, const size_t numShots, - const std::optional& custom1, - const std::optional& custom2, - const std::optional& custom3, - const std::optional& custom4, - const std::optional& custom5) const { - QDMI_Job job = nullptr; - qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); - Job jobWrapper{job}; // RAII wrapper to prevent leaks in case of exceptions - - // Set program format - qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, - QDMI_JOB_PARAMETER_PROGRAMFORMAT, - sizeof(format), &format), - "Setting program format"); - - // Set program - qdmi::throwIfError( - QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, - program.size() + 1, program.c_str()), - "Setting program"); - - // Set number of shots - qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, - QDMI_JOB_PARAMETER_SHOTSNUM, - sizeof(numShots), &numShots), - "Setting number of shots"); - - // Set custom parameters if provided - if (custom1.has_value()) { - qdmi::throwIfError( - QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM1, - custom1->size() + 1, custom1->c_str()), - "Setting custom1"); - } - if (custom2.has_value()) { - qdmi::throwIfError( - QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM2, - custom2->size() + 1, custom2->c_str()), - "Setting custom2"); - } - if (custom3.has_value()) { - qdmi::throwIfError( - QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM3, - custom3->size() + 1, custom3->c_str()), - "Setting custom3"); - } - if (custom4.has_value()) { - qdmi::throwIfError( - QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM4, - custom4->size() + 1, custom4->c_str()), - "Setting custom4"); - } - if (custom5.has_value()) { - qdmi::throwIfError( - QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM5, - custom5->size() + 1, custom5->c_str()), - "Setting custom5"); - } - - // Submit the job - qdmi::throwIfError(QDMI_job_submit(jobWrapper), "Submitting job"); - return jobWrapper; -} - QDMI_Job_Status Job::check() const { QDMI_Job_Status status{}; qdmi::throwIfError(QDMI_job_check(job_.get(), &status), diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 60042318ae..db83f7b12f 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -556,22 +556,36 @@ c = measure q;)"; EXPECT_EQ(job.check(), QDMI_JOB_STATUS_DONE); } -TEST_F(DDSimulatorDeviceTest, SubmitJobAcceptsCustomParameters) { +TEST_F(DDSimulatorDeviceTest, SubmitJobCustom1SupportedTypes) { const std::string qasm3Program = R"( OPENQASM 3.0; qubit[1] q; bit[1] c; c[0] = measure q[0]; )"; - const std::optional custom1 = "custom_value1"; - const std::optional custom2 = "custom_value2"; - const std::optional custom3 = "custom_value3"; - const std::optional custom4 = "custom_value4"; - const std::optional custom5 = "custom_value5"; - - EXPECT_THROW(std::ignore = device.submitJob( - qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, custom1, - custom2, custom3, custom4, custom5), + + auto submitWithCustom1 = [&](auto custom1) { + try { + const auto job = device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, + 10, custom1); + EXPECT_NO_THROW(std::ignore = job.getNumShots()); + } catch (const std::runtime_error&) { + GTEST_SKIP() << "Custom job parameter not supported by backend"; + } + }; + + submitWithCustom1(std::optional("custom_value1")); + submitWithCustom1(std::optional(42)); + submitWithCustom1(std::optional(3.14f)); + submitWithCustom1(std::optional(2.718)); +} + +TEST_F(DDSimulatorDeviceTest, SubmitJobCustom1UnsupportedType) { + const std::string qasm3Program = R"(OPENQASM 3.0;)"; + const std::optional custom1 = true; + + EXPECT_THROW(static_cast(device.submitJob( + qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, custom1)), std::runtime_error); } From aefb7980d372a50264f2b7e0a9cffd7e92a0c7a6 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 10 Jul 2026 14:40:03 +0200 Subject: [PATCH 06/10] =?UTF-8?q?=E2=8F=AA=20revert=20driver=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/qdmi/driver/Driver.cpp | 42 ++++---------------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/src/qdmi/driver/Driver.cpp b/src/qdmi/driver/Driver.cpp index 4427cd9180..c2c1dba036 100644 --- a/src/qdmi/driver/Driver.cpp +++ b/src/qdmi/driver/Driver.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -33,6 +32,8 @@ #ifdef _WIN32 #include + +#include #else #include #endif // _WIN32 @@ -92,31 +93,7 @@ namespace { reinterpret_cast(GetProcAddress(static_cast((lib)), (sym))) #define DL_CLOSE(lib) FreeLibrary(static_cast((lib))) #else -namespace { -/// Loads the device library with the given name, searching in the driver -/// directory if no path is specified. -[[nodiscard]] auto loadDeviceLibrary(const char* libName) -> void* { - const auto requested = std::filesystem::path(libName); - - if (requested.has_parent_path()) { - return dlopen(libName, RTLD_NOW | RTLD_LOCAL); - } - - Dl_info info{}; - if (dladdr(reinterpret_cast(&loadDeviceLibrary), &info) != 0 && - info.dli_fname != nullptr) { - const auto path = - std::filesystem::path(info.dli_fname).parent_path() / requested; - if (auto* handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); - handle != nullptr) { - return handle; - } - } - return dlopen(libName, RTLD_NOW | RTLD_LOCAL); -} -} // namespace - -#define DL_OPEN(lib) loadDeviceLibrary((lib)) +#define DL_OPEN(lib) dlopen((lib), RTLD_NOW | RTLD_LOCAL) #define DL_SYM(lib, sym) dlsym((lib), (sym)) #define DL_CLOSE(lib) dlclose((lib)) #endif @@ -298,16 +275,6 @@ namespace { return QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT; case QDMI_JOB_PARAMETER_SHOTSNUM: return QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM; - case QDMI_JOB_PARAMETER_CUSTOM1: - return QDMI_DEVICE_JOB_PARAMETER_CUSTOM1; - case QDMI_JOB_PARAMETER_CUSTOM2: - return QDMI_DEVICE_JOB_PARAMETER_CUSTOM2; - case QDMI_JOB_PARAMETER_CUSTOM3: - return QDMI_DEVICE_JOB_PARAMETER_CUSTOM3; - case QDMI_JOB_PARAMETER_CUSTOM4: - return QDMI_DEVICE_JOB_PARAMETER_CUSTOM4; - case QDMI_JOB_PARAMETER_CUSTOM5: - return QDMI_DEVICE_JOB_PARAMETER_CUSTOM5; default: return QDMI_DEVICE_JOB_PARAMETER_MAX; } @@ -319,8 +286,7 @@ QDMI_Job_impl_d::~QDMI_Job_impl_d() { } auto QDMI_Job_impl_d::setParameter(QDMI_Job_Parameter param, const size_t size, const void* value) const -> int { - if ((value != nullptr && size == 0) || - IS_INVALID_ARGUMENT(param, QDMI_JOB_PARAMETER)) { + if ((value != nullptr && size == 0) || param >= QDMI_JOB_PARAMETER_MAX) { return QDMI_ERROR_INVALIDARGUMENT; } return device_->getLibrary().device_job_set_parameter( From 807512553699b47d0970bc0971c9c727e8017dec Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 10 Jul 2026 14:50:02 +0200 Subject: [PATCH 07/10] =?UTF-8?q?=E2=8F=AA=20revert=20driver=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/qdmi/driver/Driver.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/qdmi/driver/Driver.cpp b/src/qdmi/driver/Driver.cpp index c2c1dba036..18b85434c7 100644 --- a/src/qdmi/driver/Driver.cpp +++ b/src/qdmi/driver/Driver.cpp @@ -275,6 +275,16 @@ namespace { return QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT; case QDMI_JOB_PARAMETER_SHOTSNUM: return QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM; + case QDMI_JOB_PARAMETER_CUSTOM1: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM1; + case QDMI_JOB_PARAMETER_CUSTOM2: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM2; + case QDMI_JOB_PARAMETER_CUSTOM3: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM3; + case QDMI_JOB_PARAMETER_CUSTOM4: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM4; + case QDMI_JOB_PARAMETER_CUSTOM5: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM5; default: return QDMI_DEVICE_JOB_PARAMETER_MAX; } @@ -286,7 +296,8 @@ QDMI_Job_impl_d::~QDMI_Job_impl_d() { } auto QDMI_Job_impl_d::setParameter(QDMI_Job_Parameter param, const size_t size, const void* value) const -> int { - if ((value != nullptr && size == 0) || param >= QDMI_JOB_PARAMETER_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(param, QDMI_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } return device_->getLibrary().device_job_set_parameter( From 31b22b2091a120b76c4728cb9264939fc705b8aa Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 10 Jul 2026 16:08:59 +0200 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=9A=A7=20add=20object/variant=20app?= =?UTF-8?q?roach?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/fomac/fomac.cpp | 50 +++++++++++++++++----- include/mqt-core/fomac/FoMaC.hpp | 71 ++++++++++++++++---------------- python/mqt/core/fomac.pyi | 10 ++--- test/fomac/test_fomac.cpp | 25 +++-------- test/python/fomac/test_fomac.py | 22 +++++----- 5 files changed, 98 insertions(+), 80 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 2a6595a969..206d1593fa 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -32,6 +32,28 @@ namespace mqt { namespace nb = nanobind; using namespace nb::literals; +namespace { +[[nodiscard]] auto customJobParameterFromPython(const nb::object& value) + -> std::optional { + if (value.is_none()) { + return std::nullopt; + } + if (nb::isinstance(value)) { + return nb::cast(value); + } + if (nb::isinstance(value)) { + return nb::cast(value); + } + if (nb::isinstance(value)) { + return nb::cast(value); + } + if (nb::isinstance(value)) { + return nb::cast(value); + } + throw nb::type_error("custom parameter must be str, int, float, or bool"); +} +} // namespace + NB_MODULE(MQT_CORE_MODULE_NAME, m) { // Session class auto session = nb::class_( @@ -273,18 +295,26 @@ All authentication parameters are optional and can be provided as keyword argume "submit_job", [](const fomac::Device& self, const std::string& program, const QDMI_Program_Format programFormat, const size_t numShots, - const std::optional& custom1, - const std::optional& custom2, - const std::optional& custom3, - const std::optional& custom4, - const std::optional& custom5) { - return self.submitJob(program, programFormat, numShots, custom1, - custom2, custom3, custom4, custom5); + const nb::object& custom1, const nb::object& custom2, + const nb::object& custom3, const nb::object& custom4, + const nb::object& custom5) { + return self.submitJob(program, programFormat, numShots, + customJobParameterFromPython(custom1), + customJobParameterFromPython(custom2), + customJobParameterFromPython(custom3), + customJobParameterFromPython(custom4), + customJobParameterFromPython(custom5)); }, "program"_a, "program_format"_a, "num_shots"_a, nb::kw_only(), - "custom1"_a = std::nullopt, "custom2"_a = std::nullopt, - "custom3"_a = std::nullopt, "custom4"_a = std::nullopt, - "custom5"_a = std::nullopt, nb::rv_policy::reference_internal, + "custom1"_a = nb::none(), "custom2"_a = nb::none(), + "custom3"_a = nb::none(), "custom4"_a = nb::none(), + "custom5"_a = nb::none(), nb::rv_policy::reference_internal, + nb::sig("def submit_job(self, program: str, program_format: " + "ProgramFormat, num_shots: int, *, custom1: str | int | float | " + "bool | None = None, custom2: str | int | float | bool | None " + "= None, custom3: str | int | float | bool | None = None, " + "custom4: str | int | float | bool | None = None, custom5: str " + "| int | float | bool | None = None) -> Job"), "Submits a job to the device."); device.def("__repr__", [](const fomac::Device& dev) { diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 8d49e47b3d..79749c940a 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -28,9 +28,12 @@ #include #include #include +#include #include namespace fomac { +using CustomJobParameter = std::variant; + /** * @brief Concept for ranges that are contiguous in memory and can be * constructed with a size. @@ -317,18 +320,14 @@ class Device { [[nodiscard]] std::vector getSupportedProgramFormats() const; - template - /// @see QDMI_job_submit - [[nodiscard]] Job - submitJob(const std::string& program, QDMI_Program_Format format, - size_t numShots, const std::optional& custom1 = std::nullopt, - const std::optional& custom2 = std::nullopt, - const std::optional& custom3 = std::nullopt, - const std::optional& custom4 = std::nullopt, - const std::optional& custom5 = std::nullopt) const; + [[nodiscard]] Job submitJob( + const std::string& program, QDMI_Program_Format format, size_t numShots, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) const; auto operator<=>(const Device&) const noexcept = default; @@ -402,26 +401,28 @@ class Device { friend class Session; - template - static void setCustomJobParam(QDMI_Job job, QDMI_Job_Parameter param, - const std::optional& value) { + static void + setCustomJobParam(QDMI_Job job, QDMI_Job_Parameter param, + const std::optional& value) { if (!value.has_value()) { return; } - if constexpr (std::is_same_v) { - qdmi::throwIfError( - QDMI_job_set_parameter(job, param, value->size() + 1, value->c_str()), - "Setting custom parameter"); - } else if constexpr (std::is_same_v || std::is_same_v || - std::is_same_v) { - qdmi::throwIfError( - QDMI_job_set_parameter(job, param, sizeof(T), &value.value()), - "Setting custom parameter"); - } else { - throw std::runtime_error("Unsupported custom job parameter type, must be " - "string, int, float, or double"); - } + std::visit( + [&](const auto& customValue) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + qdmi::throwIfError(QDMI_job_set_parameter(job, param, + customValue.size() + 1, + customValue.c_str()), + "Setting custom parameter"); + } else { + qdmi::throwIfError( + QDMI_job_set_parameter(job, param, sizeof(T), &customValue), + "Setting custom parameter"); + } + }, + value.value()); } }; @@ -528,14 +529,14 @@ static_assert(!std::is_copy_assignable()); static_assert(std::is_move_constructible()); static_assert(std::is_move_assignable()); -template -Job Device::submitJob(const std::string& program, - const QDMI_Program_Format format, const size_t numShots, - const std::optional& custom1, - const std::optional& custom2, - const std::optional& custom3, - const std::optional& custom4, - const std::optional& custom5) const { +inline Job +Device::submitJob(const std::string& program, const QDMI_Program_Format format, + const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) const { QDMI_Job job = nullptr; qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); Job jobWrapper{job}; diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 84387483e5..a8ce81b242 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -257,11 +257,11 @@ class Device: program_format: ProgramFormat, num_shots: int, *, - custom1: str | None = None, - custom2: str | None = None, - custom3: str | None = None, - custom4: str | None = None, - custom5: str | None = None, + custom1: str | float | bool | None = None, + custom2: str | float | bool | None = None, + custom3: str | float | bool | None = None, + custom4: str | float | bool | None = None, + custom5: str | float | bool | None = None, ) -> Job: """Submits a job to the device.""" diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index db83f7b12f..d631612508 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace fomac { @@ -557,12 +558,7 @@ c = measure q;)"; } TEST_F(DDSimulatorDeviceTest, SubmitJobCustom1SupportedTypes) { - const std::string qasm3Program = R"( -OPENQASM 3.0; -qubit[1] q; -bit[1] c; -c[0] = measure q[0]; -)"; + const std::string qasm3Program = R"(OPENQASM 3.0;)"; auto submitWithCustom1 = [&](auto custom1) { try { @@ -574,19 +570,10 @@ c[0] = measure q[0]; } }; - submitWithCustom1(std::optional("custom_value1")); - submitWithCustom1(std::optional(42)); - submitWithCustom1(std::optional(3.14f)); - submitWithCustom1(std::optional(2.718)); -} - -TEST_F(DDSimulatorDeviceTest, SubmitJobCustom1UnsupportedType) { - const std::string qasm3Program = R"(OPENQASM 3.0;)"; - const std::optional custom1 = true; - - EXPECT_THROW(static_cast(device.submitJob( - qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, custom1)), - std::runtime_error); + submitWithCustom1(std::string("custom")); + submitWithCustom1(42); + submitWithCustom1(3.14); + submitWithCustom1(true); } TEST_F(DDSimulatorDeviceTest, SubmitJobPreservesNumShots) { diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 8790efb295..42c1264ea6 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -422,17 +422,17 @@ def test_device_submit_job_returns_valid_job(ddsim_device: Device) -> None: def test_device_submit_job_handles_custom_parameters(ddsim_device: Device) -> None: - """Test that submit_job handles custom job parameters (even though no current device uses them).""" - with pytest.raises(RuntimeError, match=r"Setting custom job parameter 1: Not supported\."): - ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom1="cstm") - with pytest.raises(RuntimeError, match=r"Setting custom job parameter 2: Not supported\."): - ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom2="cstm") - with pytest.raises(RuntimeError, match=r"Setting custom job parameter 3: Not supported\."): - ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom3="cstm") - with pytest.raises(RuntimeError, match=r"Setting custom job parameter 4: Not supported\."): - ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom4="cstm") - with pytest.raises(RuntimeError, match=r"Setting custom job parameter 5: Not supported\."): - ddsim_device.submit_job("""OPENQASM 3.0;""", ProgramFormat.QASM3, 1, custom5="cstm") + """Test that submit_job forwards custom job parameters to DDSIM.""" + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom1="value") + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom2="value") + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom3="value") + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom4="value") + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom5="value") def test_device_submit_job_preserves_num_shots(ddsim_device: Device) -> None: From afc72a4de2edd2a0d16375a12c9fcaef5dfd63fe Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 10 Jul 2026 16:15:53 +0200 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=8E=A8=20fix=20stubs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/mqt/core/fomac.pyi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index a8ce81b242..06c1110e8e 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -257,11 +257,11 @@ class Device: program_format: ProgramFormat, num_shots: int, *, - custom1: str | float | bool | None = None, - custom2: str | float | bool | None = None, - custom3: str | float | bool | None = None, - custom4: str | float | bool | None = None, - custom5: str | float | bool | None = None, + custom1: str | int | float | bool | None = None, # noqa: PYI041 + custom2: str | int | float | bool | None = None, # noqa: PYI041 + custom3: str | int | float | bool | None = None, # noqa: PYI041 + custom4: str | int | float | bool | None = None, # noqa: PYI041 + custom5: str | int | float | bool | None = None, # noqa: PYI041 ) -> Job: """Submits a job to the device.""" From cd35644288cd9914fe18e3e2d16eac0f5a715287 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 10 Jul 2026 16:53:55 +0200 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=8E=A8=20calm=20lint=20and=20imporv?= =?UTF-8?q?e=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/mqt-core/fomac/FoMaC.hpp | 2 +- test/fomac/test_fomac.cpp | 1 - test/python/fomac/test_fomac.py | 23 ++++++++++++++--------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 79749c940a..4440d9edf6 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -14,11 +14,11 @@ #include "qdmi/types.h" #include +#include #include #include #include -#include #include #include #include diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index d631612508..209defa9b9 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include namespace fomac { diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 42c1264ea6..37b826e416 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -421,18 +421,23 @@ def test_device_submit_job_returns_valid_job(ddsim_device: Device) -> None: assert job.num_shots == 100 -def test_device_submit_job_handles_custom_parameters(ddsim_device: Device) -> None: +@pytest.mark.parametrize("custom_kwarg", ["custom1", "custom2", "custom3", "custom4", "custom5"]) +def test_device_submit_job_forwards_custom_parameters(ddsim_device: Device, custom_kwarg: str) -> None: """Test that submit_job forwards custom job parameters to DDSIM.""" with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): - ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom1="value") - with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): - ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom2="value") - with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): - ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom3="value") - with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): - ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom4="value") + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, **{custom_kwarg: "value"}) + + +@pytest.mark.parametrize("custom_value", ["value", 42, 1.5, True]) +def test_device_submit_job_accepts_custom_parameter_types(ddsim_device: Device, custom_value: object) -> None: + """Test that submit_job accepts supported custom parameter types.""" with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): - ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom5="value") + ddsim_device.submit_job( + "OPENQASM 3.0;", + ProgramFormat.QASM3, + 1, + custom1=cast("str | int | float | bool", custom_value), + ) def test_device_submit_job_preserves_num_shots(ddsim_device: Device) -> None: