From ca259f7271f16163920788d8311e080ec19c800d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erc=C3=BCment=20Kaya?= Date: Wed, 17 Jun 2026 17:32:49 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20C-bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 3 ++- include/mqss-c/client.h | 48 +++++++++++++++++++++++++++++++++++++++ include/mqss-c/job.h | 46 +++++++++++++++++++++++++++++++++++++ include/mqss-c/resource.h | 40 ++++++++++++++++++++++++++++++++ include/mqss/client.h | 8 +++++++ include/mqss/resource.h | 10 +++++--- src/client.cpp | 38 +++++++++++++++++++++++++++++++ src/job.cpp | 9 ++++++++ src/resource.cpp | 35 ++++++++++++++++++++++++++++ 9 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 include/mqss-c/client.h create mode 100644 include/mqss-c/job.h create mode 100644 include/mqss-c/resource.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d861af..821c84e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ project( MQSS-Client VERSION 0.1 DESCRIPTION "MQSS Client" - LANGUAGES CXX) + LANGUAGES CXX C) # Generate compile_commands.json to make it easier to work with clang based tools set(CMAKE_EXPORT_COMPILE_COMMANDS @@ -33,6 +33,7 @@ option(BUILD_TESTS "Build the tests" OFF) option(BUILD_UNIT_TESTS "Build the unit tests" OFF) option(BUILD_INTEGRATION_TESTS "Build the unit tests" OFF) option(ENABLE_COVERAGE "Enabling coverage" OFF) +option(BUILD_BINDINGS "Build Python Bindings" OFF) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") diff --git a/include/mqss-c/client.h b/include/mqss-c/client.h new file mode 100644 index 0000000..35926f6 --- /dev/null +++ b/include/mqss-c/client.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 - 2026 MQSS Project + * All rights reserved. + * + * Licensed under the Apache License v2.0 with LLVM Exceptions (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifdef __cplusplus +extern "C" { +#endif +#include "job.h" +#include "resource.h" + +#include + +typedef struct MQSSOpaqueClient* MQSSClientRef; + +MQSSClientRef mqssClientCreateClient(char* token, char* urlOrQueue, bool isHpc); + +MQSSResourceRef* mqssClientGetAllResources(MQSSClientRef client, int* size); + +MQSSResourceRef* mqssClientGetResourceInfo(MQSSClientRef client, + char* resourceName); + +int mqssClientSubmitJob(MQSSClientRef client, MQSSJobRef job); + +void mqssClientCancelJob(MQSSClientRef client, MQSSJobRef job); + +MQSSJobResultRef mqssClientGetJobResult(MQSSClientRef client, MQSSJobRef job, bool wait, + unsigned int timeout); + +int mqssClientGetNumberPendingJobs(MQSSClientRef client, char* resourceName, + int pendingJobNumber); +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/mqss-c/job.h b/include/mqss-c/job.h new file mode 100644 index 0000000..eb0d210 --- /dev/null +++ b/include/mqss-c/job.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 - 2026 MQSS Project + * All rights reserved. + * + * Licensed under the Apache License v2.0 with LLVM Exceptions (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + + +#ifdef __cplusplus +extern "C" { +#endif +#include +typedef struct MQSSOpaqueJob *MQSSJobRef; + +typedef struct MQSSOpaqueJobResult *MQSSJobResultRef; + +MQSSJobRef mqssClientCreateCircuitJob(char* circuit, + char* circuitFormat, char* resourceName, + unsigned int shots, bool noModify, bool queued); + +int mqssClientCreateHamiltonianJob(MQSSJobRef job, char* resourceName, + char* interactionStr, char* coefficientsStr); + +int mqssClientGetJobResultCounts(MQSSJobResultRef jobResult, char** bitstreams, int* counts, int size); + +int mqssClientGetJobResultCompletedTimestamp(MQSSJobResultRef jobResult, unsigned int completedTimestamp); + +int mqssClientGetJobResultSubmittedTimestamp(MQSSJobResultRef jobResult, unsigned int submittedTimestamp); + +int mqssClientGetJobResultScheduledTimestamp(MQSSJobResultRef jobResult, unsigned int scheduledTimestamp); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/mqss-c/resource.h b/include/mqss-c/resource.h new file mode 100644 index 0000000..9ae91c6 --- /dev/null +++ b/include/mqss-c/resource.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 - 2026 MQSS Project + * All rights reserved. + * + * Licensed under the Apache License v2.0 with LLVM Exceptions (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#ifdef __cplusplus +extern "C" { +#endif +#include + +typedef struct MQSSOpaqueResource *MQSSResourceRef; + +typedef struct MQSSOpaqueGate *MQSSGateRef; + +int mqssClientResourceGetInfo(MQSSResourceRef resource, char** name, + unsigned* qubitCount, bool* online, + int** couplingMap, MQSSGateRef** nativeGateset, + unsigned int* gateCount); + +int mqssClientResourceGetGateInfo(MQSSGateRef gate, unsigned int qubitNumber, + unsigned int parameterNumber, + int* supportedQubits); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/mqss/client.h b/include/mqss/client.h index 222c415..ec58d04 100644 --- a/include/mqss/client.h +++ b/include/mqss/client.h @@ -32,6 +32,14 @@ #define MQP_DEFAULT_URL "https://portal.quantum.lrz.de:4000/v1/" +template inline T* unwrap(RefT ref) { + return reinterpret_cast(ref); +} + +template inline RefT wrap(T* ptr) { + return reinterpret_cast(ptr); +} + namespace mqss::client { class MQSSBaseClient { diff --git a/include/mqss/resource.h b/include/mqss/resource.h index ba6bbf8..4d4c8cc 100644 --- a/include/mqss/resource.h +++ b/include/mqss/resource.h @@ -33,6 +33,12 @@ class Gate { mParameterNumber(parameterNumber), mSupportedQubits(std::move(supportedQubits)) {} + Gate(const Gate&) = default; + Gate& operator=(const Gate&) = default; + + Gate(Gate&&) = default; + Gate& operator=(Gate&&) = default; + const std::string& getName() const noexcept { return mName; } const unsigned int& getQubitNumber() const noexcept { return mQubitNumber; } @@ -69,9 +75,7 @@ class Resource { return mCouplingMap; } - const std::vector& getNativeGateset() const noexcept { - return mNativeGateset; - } + std::vector& getNativeGateset() { return mNativeGateset; } private: std::string mName; diff --git a/src/client.cpp b/src/client.cpp index 36eb940..112cb27 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -21,6 +21,7 @@ #include "clients/hpc_client.h" #include "clients/rest_client.h" +#include "mqss-c/client.h" #include #include @@ -139,3 +140,40 @@ int MQSSClient::getNumberPendingJobs(const std::string& resource) const { return parsed.value("num_pending_jobs", -1); } + +MQSSClientRef mqssClientCreateClient(char* token, char* urlOrQueue, + bool isHpc) { + + return wrap(new MQSSClient(token, urlOrQueue, isHpc)); +} +MQSSResourceRef* mqssClientGetAllResources(MQSSClientRef client, int* size) { + auto resources_ = unwrap(client)->getAllResources(); + + auto resources = + (MQSSResourceRef*)malloc(resources_.size() * sizeof(MQSSResourceRef)); + + for (size_t i = 0; i < resources_.size(); ++i) { + resources[i] = wrap(new Resource(resources_[i])); + } + + *size = (int)resources_.size(); + return resources; +} + +int mqssClientSubmitJob(MQSSClientRef client, MQSSJobRef job) { + auto uuid = + unwrap(client)->submitJob(*unwrap(job)); + if (!uuid.has_value()) { + return -1; + } + return std::stoi(*uuid); +} + +MQSSJobResultRef mqssClientGetJobResult(MQSSClientRef client, MQSSJobRef job, + bool wait, unsigned int timeout) { + + std::unique_ptr jobResult = unwrap(client)->getJobResult(*unwrap(job), + wait, timeout); + std::cout << jobResult->getTimestampCompleted() << "\n"; + return wrap(jobResult.get()); +} \ No newline at end of file diff --git a/src/job.cpp b/src/job.cpp index d5b38b0..04dac8e 100644 --- a/src/job.cpp +++ b/src/job.cpp @@ -18,6 +18,8 @@ */ #include "mqss/job.h" +#include "mqss-c/job.h" +#include "mqss/client.h" using namespace mqss::client; @@ -96,3 +98,10 @@ JobResult::JobResult(const nlohmann::json& parsed) { mTimestampSubmitted = parsed.at("timestamp_submitted").get(); mTimestampScheduled = parsed.at("timestamp_scheduled").get(); } + +MQSSJobRef mqssClientCreateCircuitJob(char* circuit, + char* circuitFormat, char* resourceName, + unsigned int shots, bool noModify, bool queued) { + return wrap(new CircuitJobRequest( + circuit, circuitFormat, resourceName, shots, noModify, queued)); +} diff --git a/src/resource.cpp b/src/resource.cpp index e0635f4..d6fa523 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -19,6 +19,8 @@ #include "mqss/resource.h" +#include "mqss-c/resource.h" +#include "mqss/client.h" #include using namespace mqss::client; @@ -170,3 +172,36 @@ Resource::Resource(const nlohmann::json& json) { mCouplingMap = std::move(couplingMap); mNativeGateset = std::move(nativeGateset); } + +int mqssClientResourceGetInfo(MQSSResourceRef resource, char** name, + unsigned* qubitCount, bool* online, + int** couplingMap, MQSSGateRef** nativeGateset, + unsigned int* gateCount) { + auto* resource_ = unwrap(resource); + if (resource_ == nullptr) + return -2; + + // Name + if (asprintf(name, "%s", resource_->getName().c_str()) < 0) + return -3; + + *qubitCount = resource_->getQubitCount(); + *online = resource_->isOnline(); + + *couplingMap = nullptr; + + auto& gates = resource_->getNativeGateset(); + + *gateCount = gates.size(); + + *nativeGateset = (MQSSGateRef*)malloc(sizeof(MQSSGateRef) * gates.size()); + + if (!*nativeGateset) + return -4; + + for (size_t i = 0; i < gates.size(); ++i) { + (*nativeGateset)[i] = wrap(&gates[i]); + } + + return 0; +} \ No newline at end of file From e121edff08c743b2d4063dc4cf1cb6428aafd330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erc=C3=BCment=20Kaya?= Date: Mon, 22 Jun 2026 16:11:02 +0200 Subject: [PATCH 2/2] additional implementations --- bindings/resource.cpp | 6 ++- include/mqss-c/client.h | 6 +-- include/mqss-c/job.h | 26 +++++---- include/mqss-c/resource.h | 6 +-- include/mqss/resource.h | 2 +- src/client.cpp | 10 ++-- src/job.cpp | 108 ++++++++++++++++++++++++++++++++++++-- src/resource.cpp | 3 +- 8 files changed, 138 insertions(+), 29 deletions(-) diff --git a/bindings/resource.cpp b/bindings/resource.cpp index 2aedbbe..ca2d68a 100644 --- a/bindings/resource.cpp +++ b/bindings/resource.cpp @@ -34,8 +34,10 @@ void registerResourceInterface(const py::module& m) { py::class_(m, "Gate") .def_property_readonly("name", &mqss::client::Gate::getName) - .def_property_readonly("qubit_number", &mqss::client::Gate::getQubitNumber) - .def_property_readonly("parameter_number", &mqss::client::Gate::getParameterNumber) + .def_property_readonly("qubit_number", + &mqss::client::Gate::getQubitNumber) + .def_property_readonly("parameter_number", + &mqss::client::Gate::getParameterNumber) .def_property_readonly("supported_qubits", &mqss::client::Gate::getSupportedQubits); } diff --git a/include/mqss-c/client.h b/include/mqss-c/client.h index 35926f6..33e1864 100644 --- a/include/mqss-c/client.h +++ b/include/mqss-c/client.h @@ -38,11 +38,11 @@ int mqssClientSubmitJob(MQSSClientRef client, MQSSJobRef job); void mqssClientCancelJob(MQSSClientRef client, MQSSJobRef job); -MQSSJobResultRef mqssClientGetJobResult(MQSSClientRef client, MQSSJobRef job, bool wait, - unsigned int timeout); +MQSSJobResultRef mqssClientGetJobResult(MQSSClientRef client, MQSSJobRef job, + bool wait, unsigned int timeout); int mqssClientGetNumberPendingJobs(MQSSClientRef client, char* resourceName, int pendingJobNumber); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/include/mqss-c/job.h b/include/mqss-c/job.h index eb0d210..446368d 100644 --- a/include/mqss-c/job.h +++ b/include/mqss-c/job.h @@ -17,30 +17,34 @@ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ - +#include #ifdef __cplusplus extern "C" { #endif #include -typedef struct MQSSOpaqueJob *MQSSJobRef; +typedef struct MQSSOpaqueJob* MQSSJobRef; -typedef struct MQSSOpaqueJobResult *MQSSJobResultRef; +typedef struct MQSSOpaqueJobResult* MQSSJobResultRef; -MQSSJobRef mqssClientCreateCircuitJob(char* circuit, - char* circuitFormat, char* resourceName, - unsigned int shots, bool noModify, bool queued); +MQSSJobRef mqssClientCreateCircuitJob(char* circuit, char* circuitFormat, + char* resourceName, unsigned int shots, + bool noModify, bool queued); int mqssClientCreateHamiltonianJob(MQSSJobRef job, char* resourceName, char* interactionStr, char* coefficientsStr); -int mqssClientGetJobResultCounts(MQSSJobResultRef jobResult, char** bitstreams, int* counts, int size); +int mqssClientGetJobResultCounts(MQSSJobResultRef jobResult, char*** bitstreams, + int** counts, int* size); -int mqssClientGetJobResultCompletedTimestamp(MQSSJobResultRef jobResult, unsigned int completedTimestamp); +int mqssClientGetJobResultCompletedTimestamp(MQSSJobResultRef jobResult, + uint64_t* completedTimestamp); -int mqssClientGetJobResultSubmittedTimestamp(MQSSJobResultRef jobResult, unsigned int submittedTimestamp); +int mqssClientGetJobResultSubmittedTimestamp(MQSSJobResultRef jobResult, + uint64_t* submittedTimestamp); -int mqssClientGetJobResultScheduledTimestamp(MQSSJobResultRef jobResult, unsigned int scheduledTimestamp); +int mqssClientGetJobResultScheduledTimestamp(MQSSJobResultRef jobResult, + uint64_t* scheduledTimestamp); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/include/mqss-c/resource.h b/include/mqss-c/resource.h index 9ae91c6..6a6fd01 100644 --- a/include/mqss-c/resource.h +++ b/include/mqss-c/resource.h @@ -22,9 +22,9 @@ extern "C" { #endif #include -typedef struct MQSSOpaqueResource *MQSSResourceRef; +typedef struct MQSSOpaqueResource* MQSSResourceRef; -typedef struct MQSSOpaqueGate *MQSSGateRef; +typedef struct MQSSOpaqueGate* MQSSGateRef; int mqssClientResourceGetInfo(MQSSResourceRef resource, char** name, unsigned* qubitCount, bool* online, @@ -37,4 +37,4 @@ int mqssClientResourceGetGateInfo(MQSSGateRef gate, unsigned int qubitNumber, #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/include/mqss/resource.h b/include/mqss/resource.h index 4d4c8cc..0c41d0f 100644 --- a/include/mqss/resource.h +++ b/include/mqss/resource.h @@ -38,7 +38,7 @@ class Gate { Gate(Gate&&) = default; Gate& operator=(Gate&&) = default; - + const std::string& getName() const noexcept { return mName; } const unsigned int& getQubitNumber() const noexcept { return mQubitNumber; } diff --git a/src/client.cpp b/src/client.cpp index 112cb27..0d3ae37 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -172,8 +172,8 @@ int mqssClientSubmitJob(MQSSClientRef client, MQSSJobRef job) { MQSSJobResultRef mqssClientGetJobResult(MQSSClientRef client, MQSSJobRef job, bool wait, unsigned int timeout) { - std::unique_ptr jobResult = unwrap(client)->getJobResult(*unwrap(job), - wait, timeout); - std::cout << jobResult->getTimestampCompleted() << "\n"; - return wrap(jobResult.get()); -} \ No newline at end of file + std::unique_ptr jobResult = + unwrap(client)->getJobResult(*unwrap(job), + wait, timeout); + return wrap(jobResult.release()); +} diff --git a/src/job.cpp b/src/job.cpp index 04dac8e..019665d 100644 --- a/src/job.cpp +++ b/src/job.cpp @@ -18,6 +18,7 @@ */ #include "mqss/job.h" + #include "mqss-c/job.h" #include "mqss/client.h" @@ -99,9 +100,110 @@ JobResult::JobResult(const nlohmann::json& parsed) { mTimestampScheduled = parsed.at("timestamp_scheduled").get(); } -MQSSJobRef mqssClientCreateCircuitJob(char* circuit, - char* circuitFormat, char* resourceName, - unsigned int shots, bool noModify, bool queued) { +MQSSJobRef mqssClientCreateCircuitJob(char* circuit, char* circuitFormat, + char* resourceName, unsigned int shots, + bool noModify, bool queued) { return wrap(new CircuitJobRequest( circuit, circuitFormat, resourceName, shots, noModify, queued)); } + +int mqssClientGetJobResultCounts(MQSSJobResultRef jobResult, char*** bitstreams, + int** counts, int* size) { + if (!jobResult || !bitstreams || !counts || !size) + return -1; + + auto results = unwrap(jobResult)->getResults(); + + // Count total entries across all result maps. + size_t totalEntries = 0; + for (const auto& result : results) + totalEntries += result.size(); + + *size = static_cast(totalEntries); + + if (totalEntries == 0) { + *bitstreams = nullptr; + *counts = nullptr; + return 0; + } + + *counts = static_cast(malloc(sizeof(int) * totalEntries)); + *bitstreams = static_cast(malloc(sizeof(char*) * totalEntries)); + + if (!*counts || !*bitstreams) { + free(*counts); + free(*bitstreams); + return -1; + } + + size_t index = 0; + + for (const auto& result : results) { + for (const auto& [bitstream, count] : result) { + + (*bitstreams)[index] = static_cast(malloc(bitstream.size() + 1)); + + if (!(*bitstreams)[index]) { + for (size_t i = 0; i < index; ++i) + free((*bitstreams)[i]); + free(*bitstreams); + free(*counts); + return -1; + } + + std::memcpy((*bitstreams)[index], bitstream.c_str(), + bitstream.size() + 1); + + (*counts)[index] = static_cast(count); + + ++index; + } + } + + return 0; +} + +uint64_t parseTimestamp(const std::string& s) { + + std::tm tm = {}; + std::istringstream ss(s.substr(0, 19)); // YYYY-MM-DD HH:MM:SS + + ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); + + auto tt = std::mktime(&tm); + + auto tp = std::chrono::system_clock::from_time_t(tt); + + // Parse microseconds + auto dot = s.find('.'); + if (dot != std::string::npos) { + int micros = std::stoi(s.substr(dot + 1)); + tp += std::chrono::microseconds(micros); + } + return std::chrono::duration_cast( + tp.time_since_epoch()) + .count(); +} +int mqssClientGetJobResultCompletedTimestamp(MQSSJobResultRef jobResult, + uint64_t* completedTimestamp) { + + *completedTimestamp = + parseTimestamp(unwrap(jobResult)->getTimestampCompleted()); + return 0; +} + +int mqssClientGetJobResultSubmittedTimestamp(MQSSJobResultRef jobResult, + uint64_t* submittedTimestamp) { + + *submittedTimestamp = + parseTimestamp(unwrap(jobResult)->getTimestampSubmitted()); + return 0; +} + +int mqssClientGetJobResultScheduledTimestamp(MQSSJobResultRef jobResult, + uint64_t* scheduledTimestamp) { + + *scheduledTimestamp = + parseTimestamp(unwrap(jobResult)->getTimestampScheduled()); + return 0; +} diff --git a/src/resource.cpp b/src/resource.cpp index d6fa523..4f96752 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -21,6 +21,7 @@ #include "mqss-c/resource.h" #include "mqss/client.h" + #include using namespace mqss::client; @@ -204,4 +205,4 @@ int mqssClientResourceGetInfo(MQSSResourceRef resource, char** name, } return 0; -} \ No newline at end of file +}