Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions bindings/fomac/fomac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@
namespace nb = nanobind;
using namespace nb::literals;

namespace {
[[nodiscard]] auto customJobParameterFromPython(const nb::object& value)
-> std::optional<fomac::CustomJobParameter> {
if (value.is_none()) {
return std::nullopt;
}
if (nb::isinstance<nb::bool_>(value)) {
return nb::cast<bool>(value);
}
if (nb::isinstance<nb::str>(value)) {
return nb::cast<std::string>(value);
}
if (nb::isinstance<nb::int_>(value)) {
return nb::cast<int>(value);
}
if (nb::isinstance<nb::float_>(value)) {
return nb::cast<double>(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_<fomac::Session>(
Expand Down Expand Up @@ -269,9 +291,31 @@
&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,

Check warning on line 297 in bindings/fomac/fomac.cpp

View workflow job for this annotation

GitHub Actions / 🇨‌ Lint / 🚨 Lint

bindings/fomac/fomac.cpp:297:57 [misc-include-cleaner]

no header providing "size_t" is directly included
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 = 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) {
return "<Device name=\"" + dev.getName() + "\">";
Expand Down
74 changes: 70 additions & 4 deletions include/mqt-core/fomac/FoMaC.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
#include "qdmi/types.h"

#include <qdmi/client.h>
#include <stddef.h>

#include <algorithm>
#include <complex>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <map>
Expand All @@ -28,9 +28,12 @@
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

namespace fomac {
using CustomJobParameter = std::variant<bool, int, double, std::string>;

/**
* @brief Concept for ranges that are contiguous in memory and can be
* constructed with a size.
Expand Down Expand Up @@ -318,9 +321,13 @@ class Device {
getSupportedProgramFormats() const;

/// @see QDMI_job_submit
[[nodiscard]] Job submitJob(const std::string& program,
QDMI_Program_Format format,
size_t numShots) const;
[[nodiscard]] Job submitJob(
const std::string& program, QDMI_Program_Format format, size_t numShots,
const std::optional<CustomJobParameter>& custom1 = std::nullopt,
const std::optional<CustomJobParameter>& custom2 = std::nullopt,
const std::optional<CustomJobParameter>& custom3 = std::nullopt,
const std::optional<CustomJobParameter>& custom4 = std::nullopt,
const std::optional<CustomJobParameter>& custom5 = std::nullopt) const;

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

Expand Down Expand Up @@ -393,6 +400,30 @@ class Device {
QDMI_Device device_;

friend class Session;

static void
setCustomJobParam(QDMI_Job job, QDMI_Job_Parameter param,
const std::optional<CustomJobParameter>& value) {
if (!value.has_value()) {
return;
}

std::visit(
[&](const auto& customValue) {
using T = std::decay_t<decltype(customValue)>;
if constexpr (std::is_same_v<T, std::string>) {
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());
}
};

/**
Expand Down Expand Up @@ -498,6 +529,41 @@ static_assert(!std::is_copy_assignable<Job>());
static_assert(std::is_move_constructible<Job>());
static_assert(std::is_move_assignable<Job>());

inline Job
Device::submitJob(const std::string& program, const QDMI_Program_Format format,
const size_t numShots,
const std::optional<CustomJobParameter>& custom1,
const std::optional<CustomJobParameter>& custom2,
const std::optional<CustomJobParameter>& custom3,
const std::optional<CustomJobParameter>& custom4,
const std::optional<CustomJobParameter>& 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
Expand Down
13 changes: 12 additions & 1 deletion python/mqt/core/fomac.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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 | 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."""

def __eq__(self, arg: object, /) -> bool: ...
Expand Down
30 changes: 0 additions & 30 deletions src/fomac/FoMaC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,36 +284,6 @@ std::vector<QDMI_Program_Format> Device::getSupportedProgramFormats() const {
QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS);
}

Job Device::submitJob(const std::string& program,
const QDMI_Program_Format format,
const size_t numShots) 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");

// 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),
Expand Down
13 changes: 12 additions & 1 deletion src/qdmi/driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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(
Expand Down
19 changes: 19 additions & 0 deletions test/fomac/test_fomac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,25 @@ c = measure q;)";
EXPECT_EQ(job.check(), QDMI_JOB_STATUS_DONE);
}

TEST_F(DDSimulatorDeviceTest, SubmitJobCustom1SupportedTypes) {
const std::string qasm3Program = R"(OPENQASM 3.0;)";

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::string("custom"));
submitWithCustom1(42);
submitWithCustom1(3.14);
submitWithCustom1(true);
}

TEST_F(DDSimulatorDeviceTest, SubmitJobPreservesNumShots) {
const std::string qasm3Program = R"(
OPENQASM 3.0;
Expand Down
19 changes: 19 additions & 0 deletions test/python/fomac/test_fomac.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,25 @@ def test_device_submit_job_returns_valid_job(ddsim_device: Device) -> None:
assert job.num_shots == 100


@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, **{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,
custom1=cast("str | int | float | bool", custom_value),
)


def test_device_submit_job_preserves_num_shots(ddsim_device: Device) -> None:
"""Test that different shot counts are correctly preserved."""
qasm3_program = """
Expand Down
10 changes: 10 additions & 0 deletions test/qdmi/driver/test_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::byte, 1> 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);
}
Expand Down
Loading