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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ releases may include breaking changes.

### Added

- ✨ Add support for reopening existing QDMI jobs in C++, Python, and the QDMI
driver ([#2008]) ([**@burgholzer**])
- ✨ Add PennyLane support for gate-based QDMI devices ([#2005])
([**@burgholzer**])
- ✨ Integrate QDMI devices as MLIR compiler targets across C++, Python, and
Expand Down Expand Up @@ -720,6 +722,7 @@ for previous changelogs._

<!-- PR links -->

[#2008]: https://github.com/munich-quantum-toolkit/core/pull/2008
[#2007]: https://github.com/munich-quantum-toolkit/core/pull/2007
[#2006]: https://github.com/munich-quantum-toolkit/core/pull/2006
[#2005]: https://github.com/munich-quantum-toolkit/core/pull/2005
Expand Down
8 changes: 8 additions & 0 deletions bindings/fomac/fomac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,14 @@ when the custom slot is unsupported.)pb");
"custom5"_a = nb::none(), nb::rv_policy::reference_internal,
"Submits an exact byte payload to the device.");

device.def(
"open_job",
[](const fomac::Device& self, const std::string& jobId) {
return self.openJob(jobId);
},
"job_id"_a, nb::rv_policy::reference_internal,
"Opens an existing job by its device-provided ID.");

device.def("__repr__", [](const fomac::Device& dev) {
return "<Device name=\"" + dev.getName() + "\">";
});
Expand Down
6 changes: 3 additions & 3 deletions cmake/ExternalDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ if(BUILD_MQT_CORE_TESTS)
endif()

# cmake-format: off
set(QDMI_MINIMUM_VERSION 1.3.2
set(QDMI_MINIMUM_VERSION 1.3.3
CACHE STRING "Minimum QDMI version")
set(QDMI_VERSION 1.3.2
set(QDMI_VERSION 1.3.3
CACHE STRING "QDMI version")
set(QDMI_REV "d05a0b418f42e54e9585d2e00af8ce23e745fd83" # v1.3.2
set(QDMI_REV "3716948044e23aa50c084732aae2cc3ee913014e" # QDMI PR #485
CACHE STRING "QDMI identifier (tag, branch or commit hash)")
set(QDMI_REPO_OWNER "Munich-Quantum-Software-Stack"
CACHE STRING "QDMI repository owner (change when using a fork)")
Expand Down
10 changes: 10 additions & 0 deletions include/mqt-core/fomac/FoMaC.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,16 @@ class Device {
const std::optional<CustomJobParameter>& custom4 = std::nullopt,
const std::optional<CustomJobParameter>& custom5 = std::nullopt) const;

/**
* @brief Opens an existing job by its device-provided ID.
* @details Opening a job does not submit, clone, or modify the remote job.
* The returned handle can be used to query its state and retrieve results.
* @param jobId The nonempty opaque ID returned by @ref Job::getId.
* @throws std::runtime_error If the driver or device cannot open the job.
* @see QDMI_device_open_job
*/
[[nodiscard]] Job openJob(std::string_view jobId) const;

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

private:
Expand Down
9 changes: 9 additions & 0 deletions include/mqt-core/qdmi/driver/Driver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ struct DeviceLibrary {
/// Function pointer to @ref QDMI_device_session_create_device_job.
decltype(QDMI_device_session_create_device_job)*
device_session_create_device_job{};
/// Optional function pointer to @ref QDMI_device_session_open_device_job.
decltype(QDMI_device_session_open_device_job)*
device_session_open_device_job{};
/// Function pointer to @ref QDMI_device_job_free.
decltype(QDMI_device_job_free)* device_job_free{};
/// Function pointer to @ref QDMI_device_job_set_parameter.
Expand Down Expand Up @@ -269,6 +272,12 @@ struct QDMI_Device_impl_d {
*/
auto createJob(QDMI_Job* job) -> int;

/**
* @brief Opens an existing job for the device.
* @see QDMI_device_open_job
*/
auto openJob(const char* jobId, QDMI_Job* job) -> int;

/**
* @brief Frees the job associated with the device.
* @see QDMI_job_free
Expand Down
3 changes: 3 additions & 0 deletions python/mqt/core/fomac.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ class Device:
) -> Job:
"""Submits an exact byte payload to the device."""

def open_job(self, job_id: str) -> Job:
"""Opens an existing job by its device-provided ID."""

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

Expand Down
8 changes: 8 additions & 0 deletions src/fomac/FoMaC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,14 @@ Job Device::submitJob(const std::span<const std::byte> program,
return jobWrapper;
}

Job Device::openJob(const std::string_view jobId) const {
const std::string id{jobId};
QDMI_Job job = nullptr;
qdmi::throwIfError(QDMI_device_open_job(device_.get(), id.c_str(), &job),
"Opening job");
return Job{job, device_};
}

void Device::setCustomJobParam(QDMI_Job job, const QDMI_Job_Parameter param,
const CustomJobParameter& value) {
std::visit(
Expand Down
7 changes: 7 additions & 0 deletions src/qdmi/devices/dd/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,13 @@ int MQT_DDSIM_QDMI_device_session_create_device_job(
return session->createDeviceJob(job);
}

int MQT_DDSIM_QDMI_device_session_open_device_job(
[[maybe_unused]] MQT_DDSIM_QDMI_Device_Session session,
[[maybe_unused]] const char* jobId,
[[maybe_unused]] MQT_DDSIM_QDMI_Device_Job* job) {
return QDMI_ERROR_NOTSUPPORTED;
}

void MQT_DDSIM_QDMI_device_job_free(MQT_DDSIM_QDMI_Device_Job job) {
job->free();
}
Expand Down
7 changes: 7 additions & 0 deletions src/qdmi/devices/na/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,13 @@ int MQT_NA_QDMI_device_session_create_device_job(
return session->createDeviceJob(job);
}

int MQT_NA_QDMI_device_session_open_device_job(
[[maybe_unused]] MQT_NA_QDMI_Device_Session session,
[[maybe_unused]] const char* jobId,
[[maybe_unused]] MQT_NA_QDMI_Device_Job* job) {
return QDMI_ERROR_NOTSUPPORTED;
}

void MQT_NA_QDMI_device_job_free(MQT_NA_QDMI_Device_Job job) {
if (job != nullptr) {
job->free();
Expand Down
6 changes: 6 additions & 0 deletions src/qdmi/devices/sc/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,12 @@ int MQT_SC_QDMI_device_session_create_device_job(
return session == nullptr ? QDMI_ERROR_INVALIDARGUMENT
: session->createDeviceJob(job);
}
int MQT_SC_QDMI_device_session_open_device_job(
[[maybe_unused]] MQT_SC_QDMI_Device_Session session,
[[maybe_unused]] const char* jobId,
[[maybe_unused]] MQT_SC_QDMI_Device_Job* job) {
return QDMI_ERROR_NOTSUPPORTED;
}
void MQT_SC_QDMI_device_job_free(MQT_SC_QDMI_Device_Job job) {
if (job != nullptr) {
job->free();
Expand Down
35 changes: 35 additions & 0 deletions src/qdmi/driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName,
throw std::runtime_error("Failed to load symbol: " + symbolName); \
} \
}
#define LOAD_OPTIONAL_DYNAMIC_SYMBOL(symbol) \
{ \
const std::string symbolName = std::string(prefix) + "_QDMI_" + #symbol; \
(symbol) = reinterpret_cast<decltype(symbol)>( \
DL_SYM(libHandle_, symbolName.c_str())); \
}
//===----------------------------------------------------------------------===//

try {
Expand All @@ -136,6 +142,7 @@ DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName,
LOAD_DYNAMIC_SYMBOL(device_session_set_parameter)
// device job interface
LOAD_DYNAMIC_SYMBOL(device_session_create_device_job)
LOAD_OPTIONAL_DYNAMIC_SYMBOL(device_session_open_device_job)
LOAD_DYNAMIC_SYMBOL(device_job_free)
LOAD_DYNAMIC_SYMBOL(device_job_set_parameter)
LOAD_DYNAMIC_SYMBOL(device_job_query_property)
Expand Down Expand Up @@ -234,6 +241,7 @@ void applyOverride(std::optional<T>& value,
#undef DL_OPEN
#undef DL_SYM
#undef DL_CLOSE
#undef LOAD_OPTIONAL_DYNAMIC_SYMBOL
} // namespace qdmi

QDMI_Device_impl_d::QDMI_Device_impl_d(
Expand Down Expand Up @@ -397,6 +405,26 @@ auto QDMI_Device_impl_d::createJob(QDMI_Job* job) -> int {
return QDMI_SUCCESS;
}

auto QDMI_Device_impl_d::openJob(const char* const jobId, QDMI_Job* job)
-> int {
if (jobId == nullptr || *jobId == '\0' || job == nullptr) {
return QDMI_ERROR_INVALIDARGUMENT;
}
if (library_->device_session_open_device_job == nullptr) {
return QDMI_ERROR_NOTSUPPORTED;
}
QDMI_Device_Job deviceJob = nullptr;
const auto result = library_->device_session_open_device_job(
deviceSession_, jobId, &deviceJob);
if (result != QDMI_SUCCESS) {
return result;
}
auto uniqueJob = std::make_unique<QDMI_Job_impl_d>(deviceJob, this);
const auto it = jobs_.emplace(uniqueJob.get(), std::move(uniqueJob)).first;
*job = it->first;
return QDMI_SUCCESS;
}

auto QDMI_Device_impl_d::freeJob(QDMI_Job job) -> void {
if (job != nullptr) {
jobs_.erase(job);
Expand Down Expand Up @@ -798,6 +826,13 @@ int QDMI_device_create_job(QDMI_Device dev, QDMI_Job* job) {
return dev->createJob(job);
}

int QDMI_device_open_job(QDMI_Device dev, const char* jobId, QDMI_Job* job) {
if (dev == nullptr) {
return QDMI_ERROR_INVALIDARGUMENT;
}
return dev->openJob(jobId, job);
}

void QDMI_job_free(QDMI_Job job) {
if (job != nullptr) {
job->free();
Expand Down
6 changes: 6 additions & 0 deletions test/python/fomac/test_fomac.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,12 @@ def test_device_submit_job_preserves_num_shots(ddsim_device: Device) -> None:
assert job3.num_shots == 1000


def test_device_open_job_reports_unsupported_provider(ddsim_device: Device) -> None:
"""Expose job reopening through Python without requiring DDSIM support."""
with pytest.raises(RuntimeError, match=r"Opening job: Not supported\."):
ddsim_device.open_job("unknown")


@pytest.fixture
def submitted_job(ddsim_device: Device) -> Job:
"""Fixture that provides a submitted job for testing.
Expand Down
30 changes: 26 additions & 4 deletions test/qdmi/driver/session_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <cstring>
#include <new>
#include <string>
#include <string_view>
#include <unordered_map>

struct QDMI_Child_Device_impl_d {};
Expand All @@ -27,6 +28,8 @@ struct QDMI_Device_Session_impl_d {

struct QDMI_Device_Job_impl_d {
QDMI_Device_Session session = nullptr;
std::string id = "session-job";
bool opened = false;
};

namespace {
Expand Down Expand Up @@ -205,14 +208,33 @@ TEST_SESSION_QDMI_device_session_create_device_job(QDMI_Device_Session session,
}
// The QDMI C API transfers this allocation through an opaque raw handle.
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
*job = new (std::nothrow) QDMI_Device_Job_impl_d{session};
*job = new (std::nothrow) QDMI_Device_Job_impl_d{.session = session};
return *job == nullptr ? QDMI_ERROR_OUTOFMEM : QDMI_SUCCESS;
}

extern "C" int TEST_SESSION_QDMI_device_session_open_device_job(
QDMI_Device_Session session, const char* jobId, QDMI_Device_Job* job) {
if (session == nullptr || !session->initialized || jobId == nullptr ||
*jobId == '\0' || job == nullptr) {
return QDMI_ERROR_INVALIDARGUMENT;
}
if (std::string_view{jobId} != "session-job") {
return QDMI_ERROR_NOTFOUND;
}
// The QDMI C API transfers this allocation through an opaque raw handle.
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
*job = new (std::nothrow)
QDMI_Device_Job_impl_d{.session = session, .opened = true};
return *job == nullptr ? QDMI_ERROR_OUTOFMEM : QDMI_SUCCESS;
}

extern "C" int TEST_SESSION_QDMI_device_job_set_parameter(
QDMI_Device_Job job, QDMI_Device_Job_Parameter /*parameter*/,
size_t /*size*/, const void* /*value*/) {
return job == nullptr ? QDMI_ERROR_INVALIDARGUMENT : QDMI_SUCCESS;
if (job == nullptr) {
return QDMI_ERROR_INVALIDARGUMENT;
}
return job->opened ? QDMI_ERROR_BADSTATE : QDMI_SUCCESS;
}

extern "C" int TEST_SESSION_QDMI_device_job_query_property(
Expand All @@ -222,14 +244,14 @@ extern "C" int TEST_SESSION_QDMI_device_job_query_property(
prop != QDMI_DEVICE_JOB_PROPERTY_ID) {
return QDMI_ERROR_INVALIDARGUMENT;
}
return queryString("session-job", size, value, sizeRet);
return queryString(job->id, size, value, sizeRet);
}

extern "C" int TEST_SESSION_QDMI_device_job_submit(QDMI_Device_Job job) {
if (job == nullptr || job->session == nullptr) {
return QDMI_ERROR_INVALIDARGUMENT;
}
return QDMI_SUCCESS;
return job->opened ? QDMI_ERROR_BADSTATE : QDMI_SUCCESS;
}

extern "C" int TEST_SESSION_QDMI_device_job_cancel(QDMI_Device_Job /*job*/) {
Expand Down
62 changes: 62 additions & 0 deletions test/qdmi/driver/test_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <utility>
Expand Down Expand Up @@ -265,6 +266,15 @@ class ChildDeviceLibrary final : public qdmi::DeviceLibrary {
return driver.open(id);
}

[[nodiscard]] auto openOwnedSessionTestDevice(const std::string_view id)
-> fomac::Device {
static_cast<void>(qdmi::Driver::get().registerDeviceIfAbsent(
{.id = std::string{id},
.library = MQT_CORE_QDMI_SESSION_DEVICE,
.prefix = "TEST_SESSION"}));
return fomac::Session::openDevice(id);
}

class DriverTest : public testing::TestWithParam<const char*> {
protected:
QDMI_Session session = nullptr;
Expand Down Expand Up @@ -455,6 +465,58 @@ TEST_P(DriverTest, JobCreate) {
QDMI_ERROR_INVALIDARGUMENT);
}

TEST_P(DriverTest, JobOpen) {
QDMI_Job job = nullptr;
const auto result = QDMI_device_open_job(device, "session-job", &job);
if (result == QDMI_ERROR_NOTSUPPORTED) {
return;
}
ASSERT_EQ(result, QDMI_SUCCESS);
ASSERT_NE(job, nullptr);
QDMI_job_free(job);
EXPECT_EQ(QDMI_device_open_job(device, "", &job), QDMI_ERROR_INVALIDARGUMENT);
EXPECT_EQ(QDMI_device_open_job(device, "unknown", &job), QDMI_ERROR_NOTFOUND);
}

TEST(JobOpenTest, OpensExistingJobThroughClientApi) {
const auto ownedDevice = openOwnedSessionTestDevice("test.open-job-client");
QDMI_Device device = ownedDevice;
QDMI_Job job = nullptr;
ASSERT_EQ(QDMI_device_open_job(device, "session-job", &job), QDMI_SUCCESS);
ASSERT_NE(job, nullptr);

size_t size = 0;
ASSERT_EQ(
QDMI_job_query_property(job, QDMI_JOB_PROPERTY_ID, 0, nullptr, &size),
QDMI_SUCCESS);
std::string id(size - 1, '\0');
EXPECT_EQ(QDMI_job_query_property(job, QDMI_JOB_PROPERTY_ID, size, id.data(),
nullptr),
QDMI_SUCCESS);
EXPECT_EQ(id, "session-job");
const size_t numShots = 1;
EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_SHOTSNUM,
sizeof(numShots), &numShots),
QDMI_ERROR_BADSTATE);
EXPECT_EQ(QDMI_job_submit(job), QDMI_ERROR_BADSTATE);
QDMI_job_free(job);

EXPECT_EQ(QDMI_device_open_job(nullptr, "session-job", &job),
QDMI_ERROR_INVALIDARGUMENT);
EXPECT_EQ(QDMI_device_open_job(device, nullptr, &job),
QDMI_ERROR_INVALIDARGUMENT);
EXPECT_EQ(QDMI_device_open_job(device, "", &job), QDMI_ERROR_INVALIDARGUMENT);
EXPECT_EQ(QDMI_device_open_job(device, "session-job", nullptr),
QDMI_ERROR_INVALIDARGUMENT);
EXPECT_EQ(QDMI_device_open_job(device, "unknown", &job), QDMI_ERROR_NOTFOUND);
}

TEST(FoMaCJobTest, OpensExistingJobs) {
const auto device = openOwnedSessionTestDevice("test.open-job-fomac");
const auto job = device.openJob("session-job");
EXPECT_EQ(job.getId(), "session-job");
}

TEST_P(DriverTest, JobSetParameter) {
EXPECT_EQ(QDMI_job_set_parameter(nullptr, QDMI_JOB_PARAMETER_MAX, 0, nullptr),
QDMI_ERROR_INVALIDARGUMENT);
Expand Down
Loading