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/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 new file mode 100644 index 0000000..33e1864 --- /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 diff --git a/include/mqss-c/job.h b/include/mqss-c/job.h new file mode 100644 index 0000000..446368d --- /dev/null +++ b/include/mqss-c/job.h @@ -0,0 +1,50 @@ +/* + * 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 + */ + +#include +#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, + uint64_t* completedTimestamp); + +int mqssClientGetJobResultSubmittedTimestamp(MQSSJobResultRef jobResult, + uint64_t* submittedTimestamp); + +int mqssClientGetJobResultScheduledTimestamp(MQSSJobResultRef jobResult, + uint64_t* scheduledTimestamp); + +#ifdef __cplusplus +} +#endif diff --git a/include/mqss-c/resource.h b/include/mqss-c/resource.h new file mode 100644 index 0000000..6a6fd01 --- /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 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..0c41d0f 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..0d3ae37 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); + return wrap(jobResult.release()); +} diff --git a/src/job.cpp b/src/job.cpp index d5b38b0..019665d 100644 --- a/src/job.cpp +++ b/src/job.cpp @@ -19,6 +19,9 @@ #include "mqss/job.h" +#include "mqss-c/job.h" +#include "mqss/client.h" + using namespace mqss::client; nlohmann::json CircuitJobRequest::toJson() const { @@ -96,3 +99,111 @@ 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)); +} + +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 e0635f4..4f96752 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -19,6 +19,9 @@ #include "mqss/resource.h" +#include "mqss-c/resource.h" +#include "mqss/client.h" + #include using namespace mqss::client; @@ -170,3 +173,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; +}