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 @@ -13,6 +13,8 @@ releases may include breaking changes.
### Added

- 🐍 Start building CPython 3.15 wheels ([#2011]) ([**@denialhaag**])
- ✨ Add support for retrieving existing QDMI jobs by ID 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 @@ -722,6 +724,7 @@ for previous changelogs._
<!-- PR links -->

[#2011]: https://github.com/munich-quantum-toolkit/core/pull/2011
[#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(
"retrieve_job_by_id",
[](const fomac::Device& self, const std::string& jobId) {
return self.retrieveJobById(jobId);
},
"job_id"_a, nb::rv_policy::reference_internal,
"Retrieves 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 "a884facd38a551086eef00c3116f1c8e9a09b9c2" # 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 Retrieves 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 retrieve the job.
* @see QDMI_session_retrieve_job_by_id
*/
[[nodiscard]] Job retrieveJobById(std::string_view jobId) const;

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

private:
Expand Down
10 changes: 10 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,10 @@ 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_retrieve_device_job_by_id.
decltype(QDMI_device_session_retrieve_device_job_by_id)*
device_session_retrieve_device_job_by_id{};
/// 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 +273,12 @@ struct QDMI_Device_impl_d {
*/
auto createJob(QDMI_Job* job) -> int;

/**
* @brief Retrieves an existing job for the device.
* @see QDMI_session_retrieve_job_by_id
*/
auto retrieveJobById(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 retrieve_job_by_id(self, job_id: str) -> Job:
"""Retrieves an existing job by its device-provided ID."""

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

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

Job Device::retrieveJobById(const std::string_view jobId) const {
const std::string id{jobId};
QDMI_Job job = nullptr;
qdmi::throwIfError(
QDMI_session_retrieve_job_by_id(device_.get(), id.c_str(), &job),
"Retrieving 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_retrieve_device_job_by_id(
[[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_retrieve_device_job_by_id(
[[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_retrieve_device_job_by_id(
[[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
36 changes: 36 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_retrieve_device_job_by_id)
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::retrieveJobById(const char* const jobId, QDMI_Job* job)
-> int {
if (jobId == nullptr || *jobId == '\0' || job == nullptr) {
return QDMI_ERROR_INVALIDARGUMENT;
}
if (library_->device_session_retrieve_device_job_by_id == nullptr) {
return QDMI_ERROR_NOTSUPPORTED;
}
QDMI_Device_Job deviceJob = nullptr;
const auto result = library_->device_session_retrieve_device_job_by_id(
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,14 @@ int QDMI_device_create_job(QDMI_Device dev, QDMI_Job* job) {
return dev->createJob(job);
}

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

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


def test_device_retrieve_job_by_id_reports_unsupported_provider(
ddsim_device: Device,
) -> None:
"""Expose job retrieval through Python without requiring DDSIM support."""
with pytest.raises(RuntimeError, match=r"Retrieving job: Not supported\."):
ddsim_device.retrieve_job_by_id("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_retrieve_device_job_by_id(
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
Loading
Loading