From c0a09cb5c8fab288ddaaa309541f488a538e3c2e Mon Sep 17 00:00:00 2001 From: Ryan Hill Date: Wed, 4 Jun 2025 16:41:10 -0500 Subject: [PATCH 01/18] qBraid integration MVP (#4) * working implementation using openQasm * modified and added test files(incomplete) * fix emulate command alignment * update polling + format * update polling interval and make code more readable * remove ionq fields from target-arguments * fix formatting * Add qBraid mock python server for testing Signed-off-by: Ryan Hill * Update __init__.py Signed-off-by: Ryan Hill * QbraidTester running correctly * added documentation for qbraid --------- Signed-off-by: Ryan Hill Co-authored-by: feelerx --- .github/workflows/integration_tests.yml | 5 + docs/sphinx/targets/cpp/qbraid.cpp | 49 +++ docs/sphinx/targets/python/qbraid.py | 52 +++ docs/sphinx/using/backends/cloud.rst | 6 + docs/sphinx/using/backends/cloud/qbraid.rst | 62 ++++ .../using/backends/hardware/iontrap.rst | 68 ++++ lib/Optimizer/CodeGen/Passes.cpp | 14 + .../default/rest/helpers/CMakeLists.txt | 3 + .../rest/helpers/qbraid/CMakeLists.txt | 17 + .../helpers/qbraid/QbraidServerHelper.cpp | 295 ++++++++++++++++++ .../default/rest/helpers/qbraid/qbraid.yml | 30 ++ targettests/execution/cudaq_observe-cpp17.cpp | 56 ++++ targettests/qbraid/bug_qubit.cpp | 50 +++ targettests/qbraid/callable_kernel_arg.cpp | 50 +++ targettests/qbraid/cudaq_observe.cpp | 57 ++++ targettests/qbraid/if_jit.cpp | 45 +++ targettests/qbraid/load_value.cpp | 63 ++++ targettests/qbraid/sudoku_2x2-1.cpp | 79 +++++ targettests/qbraid/sudoku_2x2-bit_names.cpp | 103 ++++++ targettests/qbraid/sudoku_2x2-reg_name.cpp | 79 +++++ targettests/qbraid/sudoku_2x2.cpp | 78 +++++ targettests/qbraid/swap_gate.cpp | 43 +++ targettests/qbraid/test-int8_t.cpp | 48 +++ targettests/qbraid/test-int8_t_free_func.cpp | 46 +++ targettests/qbraid/variable_size_qreg.cpp | 46 +++ tpls/Stim | 2 +- tpls/cpr | 2 +- tpls/fmt | 2 +- tpls/spdlog | 2 +- unittests/backends/CMakeLists.txt | 13 +- unittests/backends/qbraid/CMakeLists.txt | 27 ++ .../qbraid/QbraidStartServerAndTest.sh.in | 43 +++ unittests/backends/qbraid/QbraidTester.cpp | 177 +++++++++++ utils/mock_qpu/__init__.py | 1 + utils/mock_qpu/qbraid/__init__.py | 240 ++++++++++++++ 35 files changed, 1944 insertions(+), 9 deletions(-) create mode 100644 docs/sphinx/targets/cpp/qbraid.cpp create mode 100644 docs/sphinx/targets/python/qbraid.py create mode 100644 docs/sphinx/using/backends/cloud/qbraid.rst create mode 100644 runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt create mode 100644 runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp create mode 100644 runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml create mode 100644 targettests/execution/cudaq_observe-cpp17.cpp create mode 100644 targettests/qbraid/bug_qubit.cpp create mode 100644 targettests/qbraid/callable_kernel_arg.cpp create mode 100644 targettests/qbraid/cudaq_observe.cpp create mode 100644 targettests/qbraid/if_jit.cpp create mode 100644 targettests/qbraid/load_value.cpp create mode 100644 targettests/qbraid/sudoku_2x2-1.cpp create mode 100644 targettests/qbraid/sudoku_2x2-bit_names.cpp create mode 100644 targettests/qbraid/sudoku_2x2-reg_name.cpp create mode 100644 targettests/qbraid/sudoku_2x2.cpp create mode 100644 targettests/qbraid/swap_gate.cpp create mode 100644 targettests/qbraid/test-int8_t.cpp create mode 100644 targettests/qbraid/test-int8_t_free_func.cpp create mode 100644 targettests/qbraid/variable_size_qreg.cpp create mode 100644 unittests/backends/qbraid/CMakeLists.txt create mode 100644 unittests/backends/qbraid/QbraidStartServerAndTest.sh.in create mode 100644 unittests/backends/qbraid/QbraidTester.cpp create mode 100644 utils/mock_qpu/qbraid/__init__.py diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 47cce0eb1e3..ee781d737b5 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -23,11 +23,16 @@ on: - iqm - oqc - orca +<<<<<<< HEAD - pasqal - qci - quantinuum - scaleway - tii +======= + - fermioniq + - qbraid +>>>>>>> 17f25cf4 (qBraid integration MVP (#4)) single_test_name: type: string required: false diff --git a/docs/sphinx/targets/cpp/qbraid.cpp b/docs/sphinx/targets/cpp/qbraid.cpp new file mode 100644 index 00000000000..4b696005582 --- /dev/null +++ b/docs/sphinx/targets/cpp/qbraid.cpp @@ -0,0 +1,49 @@ +// Compile and run with: +// ``` +// nvq++ --target qbraid qbraid.cpp -o out.x && ./out.x +// ``` +// This will submit the job to the Qbraid ideal simulator target (default). + + +#include +#include + +// Define a simple quantum kernel to execute on Qbraid. +struct ghz { + // Maximally entangled state between 5 qubits. + auto operator()() __qpu__ { + cudaq::qvector q(5); + h(q[0]); + for (int i = 0; i < 4; i++) { + x(q[i], q[i + 1]); + } + auto result = mz(q); + } +}; + +int main() { + // Submit to Qbraid asynchronously (e.g., continue executing + // code in the file until the job has been returned). + auto future = cudaq::sample_async(ghz{}); + // ... classical code to execute in the meantime ... + + // Can write the future to file: + { + std::ofstream out("saveMe.json"); + out << future; + } + + // Then come back and read it in later. + cudaq::async_result readIn; + std::ifstream in("saveMe.json"); + in >> readIn; + + // Get the results of the read in future. + auto async_counts = readIn.get(); + async_counts.dump(); + + // OR: Submit to Qbraid synchronously (e.g., wait for the job + // result to be returned before proceeding). + auto counts = cudaq::sample(ghz{}); + counts.dump(); +} diff --git a/docs/sphinx/targets/python/qbraid.py b/docs/sphinx/targets/python/qbraid.py new file mode 100644 index 00000000000..8450e3a6fd8 --- /dev/null +++ b/docs/sphinx/targets/python/qbraid.py @@ -0,0 +1,52 @@ +import cudaq + +# You only have to set the target once! No need to redefine it +# for every execution call on your kernel. +# To use different targets in the same file, you must update +# it via another call to `cudaq.set_target()` +cudaq.set_target("qbraid") + + +# Create the kernel we'd like to execute on Qbraid. +@cudaq.kernel +def kernel(): + qvector = cudaq.qvector(2) + h(qvector[0]) + x.ctrl(qvector[0], qvector[1]) + + + +# Execute on Qbraid and print out the results. + +# Option A: +# By using the asynchronous `cudaq.sample_async`, the remaining +# classical code will be executed while the job is being handled +# by IonQ. This is ideal when submitting via a queue over +# the cloud. +async_results = cudaq.sample_async(kernel) +# ... more classical code to run ... + +# We can either retrieve the results later in the program with +# ``` +# async_counts = async_results.get() +# ``` +# or we can also write the job reference (`async_results`) to +# a file and load it later or from a different process. +file = open("future.txt", "w") +file.write(str(async_results)) +file.close() + +# We can later read the file content and retrieve the job +# information and results. +same_file = open("future.txt", "r") +retrieved_async_results = cudaq.AsyncSampleResult(str(same_file.read())) + +counts = retrieved_async_results.get() +print(counts) + +# Option B: +# By using the synchronous `cudaq.sample`, the execution of +# any remaining classical code in the file will occur only +# after the job has been returned from Qbraid. +counts = cudaq.sample(kernel) +print(counts) \ No newline at end of file diff --git a/docs/sphinx/using/backends/cloud.rst b/docs/sphinx/using/backends/cloud.rst index 8c03a4398cc..2395dd6d3b1 100644 --- a/docs/sphinx/using/backends/cloud.rst +++ b/docs/sphinx/using/backends/cloud.rst @@ -7,4 +7,10 @@ CUDA-Q provides a number of options to access hardware resources (GPUs and QPUs) :maxdepth: 1 Amazon Braket (braket) +<<<<<<< HEAD Scaleway QaaS (scaleway) +======= + NVIDIA Quantum Cloud (nvqc) + Qbraid + +>>>>>>> 17f25cf4 (qBraid integration MVP (#4)) diff --git a/docs/sphinx/using/backends/cloud/qbraid.rst b/docs/sphinx/using/backends/cloud/qbraid.rst new file mode 100644 index 00000000000..91184e6b934 --- /dev/null +++ b/docs/sphinx/using/backends/cloud/qbraid.rst @@ -0,0 +1,62 @@ +QBRAID ++++++++ + +.. _qbraid-backend: + +Setting Credentials +````````````````````````` + +Programmers of CUDA-Q may access the `Qbraid Devices +`__ from either C++ or Python. Generate +an API key from your `Qbraid account `__ and export +it as an environment variable: + +.. code:: bash + + export QBRAID_API_KEY="qbraid_generated_api_key" + + +Submission from Python +````````````````````````` + + First, set the :code:`qbraid` backend. + + .. code:: python + + cudaq.set_target('qbraid') + + By default, quantum kernel code will be submitted to the IonQ simulator on qBraid. + + To emulate the qbraid's simulator locally, without submitting through the cloud, you can also set the ``emulate`` flag to ``True``. This will emit any target specific compiler diagnostics. + + .. code:: python + + cudaq.set_target('qbraid', emulate=True) + + The number of shots for a kernel execution can be set through the ``shots_count`` argument to ``cudaq.sample`` or ``cudaq.observe``. By default, the ``shots_count`` is set to 1000. + + .. code:: python + + cudaq.sample(kernel, shots_count=10000) + + To see a complete example for using Qbraid's backends, take a look at our :doc:`Python examples <../../examples/examples>`. + +Submission from C++ +````````````````````````` + To target quantum kernel code for execution using qbraid, + pass the flag ``--target qbraid`` to the ``nvq++`` compiler. + + .. code:: bash + + nvq++ --target qbraid src.cpp + + This will take the API key and handle all authentication with, and submission to, the Qbraid device. By default, quantum kernel code will be submitted to the Qbraidsimulator. + + To emulate the qbraid's machine locally, without submitting through the cloud, you can also pass the ``--emulate`` flag to ``nvq++``. This will emit any target specific compiler diagnostics, before running a noise free emulation. + + .. code:: bash + + nvq++ --emulate --target qbraid src.cpp + + To see a complete example for using IonQ's backends, take a look at our :doc:`C++ examples <../../examples/examples>`. + \ No newline at end of file diff --git a/docs/sphinx/using/backends/hardware/iontrap.rst b/docs/sphinx/using/backends/hardware/iontrap.rst index 83e25326455..3d5db2a90e4 100644 --- a/docs/sphinx/using/backends/hardware/iontrap.rst +++ b/docs/sphinx/using/backends/hardware/iontrap.rst @@ -220,6 +220,7 @@ Create a project in the Nexus portal. You can find the project ID in the URL of .. note:: +<<<<<<< HEAD Quantinuum's syntax checker for Helios (e.g., ``Helios-1SC``) only performs QIR code validation and does not return any results. Thus, it always returns an empty result set. This is different from other Quantinuum backends (e.g., ``H2-1SC``) where the syntax checker returns dummy results. As a result, when using the Helios syntax checker, we may receive this warning message: @@ -244,3 +245,70 @@ To see a complete example, take a look at :ref:`Quantinuum examples `__ from either C++ or Python. Generate +an API key from your `Qbraid account `__ and export +it as an environment variable: + +.. code:: bash + + export QBRAID_API_KEY="qbraid_generated_api_key" + + +Submitting +````````````````````````` +.. tab:: Python + + First, set the :code:`qbraid` backend. + + .. code:: python + + cudaq.set_target('qbraid') + + By default, quantum kernel code will be submitted to the IonQ simulator on qBraid. + + To emulate the qbraid's simulator locally, without submitting through the cloud, you can also set the ``emulate`` flag to ``True``. This will emit any target specific compiler diagnostics. + + .. code:: python + + cudaq.set_target('qbraid', emulate=True) + + The number of shots for a kernel execution can be set through the ``shots_count`` argument to ``cudaq.sample`` or ``cudaq.observe``. By default, the ``shots_count`` is set to 1000. + + .. code:: python + + cudaq.sample(kernel, shots_count=10000) + + To see a complete example for using Qbraid's backends, take a look at our :doc:`Python examples <../../examples/examples>`. + + +.. tab:: C++ + + To target quantum kernel code for execution using qbraid, + pass the flag ``--target qbraid`` to the ``nvq++`` compiler. + + .. code:: bash + + nvq++ --target qbraid src.cpp + + This will take the API key and handle all authentication with, and submission to, the Qbraid device. By default, quantum kernel code will be submitted to the Qbraidsimulator. + + To emulate the qbraid's machine locally, without submitting through the cloud, you can also pass the ``--emulate`` flag to ``nvq++``. This will emit any target specific compiler diagnostics, before running a noise free emulation. + + .. code:: bash + + nvq++ --emulate --target qbraid src.cpp + + To see a complete example for using IonQ's backends, take a look at our :doc:`C++ examples <../../examples/examples>`. + +>>>>>>> 17f25cf4 (qBraid integration MVP (#4)) diff --git a/lib/Optimizer/CodeGen/Passes.cpp b/lib/Optimizer/CodeGen/Passes.cpp index 8ff6c53c2d1..ce9795bf0c8 100644 --- a/lib/Optimizer/CodeGen/Passes.cpp +++ b/lib/Optimizer/CodeGen/Passes.cpp @@ -100,6 +100,17 @@ static void addFermioniqPipeline(OpPassManager &pm) { pm.addPass(createBasisConversion(options)); } +static void addQbraidPipeline(OpPassManager &pm) { + using namespace cudaq::opt; + std::string basis[] = { + "h", "s", "t", "rx", "ry", "rz", "x", "y", "z", "x(1)", + }; + BasisConversionPassOptions options; + options.basis = basis; + options.disabledPatterns = z_disabledPatterns; + pm.addPass(createBasisConversionPass(options)); +} + void cudaq::opt::registerTargetPipelines() { PassPipelineRegistration<>("anyon-cgate-set-mapping", "Convert kernels to Anyon gate set.", @@ -125,6 +136,9 @@ void cudaq::opt::registerTargetPipelines() { PassPipelineRegistration<>("fermioniq-gate-set-mapping", "Convert kernels to Fermioniq gate set.", addFermioniqPipeline); + PassPipelineRegistration<>("qbraid-gate-set-mapping", + "Convert kernels to qBraid gate set.", + addQbraidPipeline); } void cudaq::opt::registerCodeGenDialect(DialectRegistry ®istry) { diff --git a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt index 5daa54ea114..4574b6ba8fe 100644 --- a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt @@ -27,3 +27,6 @@ endif() if(CUDAQ_ENABLE_TII_BACKEND) add_subdirectory(tii) endif() +if(CUDAQ_ENABLE_QBRAID_BACKEND) + add_subdirectory(qbraid) +endif() diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt new file mode 100644 index 00000000000..05b059ecd25 --- /dev/null +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt @@ -0,0 +1,17 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # +target_sources(cudaq-rest-qpu PRIVATE QbraidServerHelper.cpp) +add_target_config(qbraid) + +add_library(cudaq-serverhelper-qbraid SHARED QbraidServerHelper.cpp ) +target_link_libraries(cudaq-serverhelper-qbraid + PUBLIC + cudaq-common + fmt::fmt-header-only +) +install(TARGETS cudaq-serverhelper-qbraid DESTINATION lib) \ No newline at end of file diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp new file mode 100644 index 00000000000..5e2bf74787e --- /dev/null +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -0,0 +1,295 @@ +#include "common/Logger.h" +#include "common/RestClient.h" +#include "common/ServerHelper.h" +#include "cudaq/Support/Version.h" +#include "cudaq/utils/cudaq_utils.h" +#include +#include +#include +#include + +namespace cudaq { + +class QbraidServerHelper : public ServerHelper { + static constexpr const char *DEFAULT_URL = "https://api.qbraid.com/api"; + static constexpr const char *DEFAULT_DEVICE = "ionq_simulator"; + static constexpr int DEFAULT_QUBITS = 29; + +public: + const std::string name() const override { return "qbraid"; } + + void initialize(BackendConfig config) override { + cudaq::info("Initializing Qbraid Backend."); + + backendConfig.clear(); + backendConfig["url"] = getValueOrDefault(config, "url", DEFAULT_URL); + backendConfig["device_id"] = getValueOrDefault(config, "device_id", DEFAULT_DEVICE); + backendConfig["user_agent"] = "cudaq/" + std::string(cudaq::getVersion()); + backendConfig["qubits"] = std::to_string(DEFAULT_QUBITS); + + backendConfig["api_key"] = getEnvVar("QBRAID_API_KEY", "", true); + backendConfig["job_path"] = backendConfig["url"] + "/quantum-jobs"; + backendConfig["results_path"] = backendConfig["url"] + "/quantum-jobs/result/"; + + backendConfig["results_output_dir"] = getValueOrDefault(config, "results_output_dir", "./qbraid_results"); + backendConfig["results_file_prefix"] = getValueOrDefault(config, "results_file_prefix", "qbraid_job_"); + + if (!config["shots"].empty()) { + backendConfig["shots"] = config["shots"]; + this->setShots(std::stoul(config["shots"])); + } else { + backendConfig["shots"] = "1000"; + this->setShots(1000); + } + + parseConfigForCommonParams(config); + + cudaq::info("Qbraid configuration initialized:"); + for (const auto &[key, value] : backendConfig) { + cudaq::info(" {} = {}", key, value); + } + + std::string resultsDir = backendConfig["results_output_dir"]; + std::filesystem::create_directories(resultsDir); + cudaq::info("Created results directory: {}", resultsDir); + } + + ServerJobPayload + createJob(std::vector &circuitCodes) override { + if (backendConfig.find("job_path") == backendConfig.end()) { + throw std::runtime_error("job_path not found in config. Was initialize() called?"); + } + + std::vector jobs; + for (auto &circuitCode : circuitCodes) { + ServerMessage job; + job["qbraidDeviceId"] = backendConfig.at("device_id"); + job["openQasm"] = circuitCode.code; + job["shots"] = std::stoi(backendConfig.at("shots")); + + if (!circuitCode.name.empty()) { + nlohmann::json tags; + tags["name"] = circuitCode.name; + job["tags"] = tags; + } + + jobs.push_back(job); + } + + return std::make_tuple(backendConfig.at("job_path"), getHeaders(), jobs); + } + + std::string extractJobId(ServerMessage &postResponse) override { + if (!postResponse.contains("qbraidJobId")) { + throw std::runtime_error("ServerMessage doesn't contain 'qbraidJobId' key."); + } + return postResponse.at("qbraidJobId"); + } + + std::string constructGetJobPath(ServerMessage &postResponse) override { + if (!postResponse.contains("qbraidJobId")) { + throw std::runtime_error("ServerMessage doesn't contain 'qbraidJobId' key."); + } + + return backendConfig.at("job_path") + "?qbraidJobId=" + postResponse.at("qbraidJobId").get(); + } + + std::string constructGetJobPath(std::string &jobId) override { + return backendConfig.at("job_path") + "?qbraidJobId=" + jobId; + } + + std::string constructGetResultsPath(const std::string &jobId) { + return backendConfig.at("results_path") + jobId; + } + + bool jobIsDone(ServerMessage &getJobResponse) override { + std::string status; + + if (getJobResponse.contains("jobsArray") && !getJobResponse["jobsArray"].empty()) { + status = getJobResponse["jobsArray"][0]["status"].get(); + cudaq::info("Job status from jobs endpoint: {}", status); + } else if (getJobResponse.contains("status")) { + status = getJobResponse["status"].get(); + cudaq::info("Job status from direct response: {}", status); + } else if (getJobResponse.contains("data") && getJobResponse["data"].contains("status")) { + status = getJobResponse["data"]["status"].get(); + cudaq::info("Job status from data object: {}", status); + } else { + cudaq::info("Unexpected job response format: {}", getJobResponse.dump()); + throw std::runtime_error("Invalid job response format"); + } + + if (status == "FAILED" || status == "COMPLETED" || status == "CANCELLED") { + saveResponseToFile(getJobResponse); + return true; + } + + return false; + } + + // Sample results with results api - with retry logic + cudaq::sample_result processResults(ServerMessage &getJobResponse, std::string &jobId) override { + int maxRetries = 5; + int waitTime = 2; + float backoffFactor = 2.0; + + for (int attempt = 0; attempt < maxRetries; ++attempt) { + try { + auto resultsPath = constructGetResultsPath(jobId); + auto headers = getHeaders(); + + cudaq::info("Fetching results using direct endpoint (attempt {}/{}): {}", attempt + 1, maxRetries, resultsPath); + RestClient client; + auto resultJson = client.get("", resultsPath, headers, true); + + if (resultJson.contains("error") && !resultJson["error"].is_null()) { + std::string errorMsg = resultJson["error"].is_string() + ? resultJson["error"].get() + : resultJson["error"].dump(); + cudaq::info("Error from results endpoint: {}", errorMsg); + + if (attempt == maxRetries - 1) { + throw std::runtime_error("Error retrieving results: " + errorMsg); + } + } else if (resultJson.contains("data") && resultJson["data"].contains("measurementCounts")) { + cudaq::info("Processing results from direct endpoint"); + CountsDictionary counts; + auto &measurements = resultJson["data"]["measurementCounts"]; + + for (const auto &[bitstring, count] : measurements.items()) { + counts[bitstring] = + count.is_number() + ? static_cast(count.get()) + : static_cast(count); + } + + std::vector execResults; + execResults.emplace_back(ExecutionResult{counts}); + return cudaq::sample_result(execResults); + } + + // If we get here, no valid data was found but also no error - retry + if (attempt < maxRetries - 1) { + int sleepTime = (attempt == 0) ? waitTime : waitTime * std::pow(backoffFactor, attempt); + cudaq::info("No valid results yet, retrying in {} seconds", sleepTime); + std::this_thread::sleep_for(std::chrono::seconds(sleepTime)); + } + + } catch (const std::exception &e) { + cudaq::info("Exception when using direct results endpoint: {}", e.what()); + if (attempt < maxRetries - 1) { + int sleepTime = (attempt == 0) ? waitTime : waitTime * std::pow(backoffFactor, attempt); + cudaq::info("Retrying in {} seconds", sleepTime); + std::this_thread::sleep_for(std::chrono::seconds(sleepTime)); + } else { + cudaq::info("Falling back to original results processing method"); + } + } + } + + // Original result processing as fallback + cudaq::info("Processing results from job response for job {}", jobId); + if (getJobResponse.contains("jobsArray") && !getJobResponse["jobsArray"].empty()) { + auto &job = getJobResponse["jobsArray"][0]; + + if (job.contains("measurementCounts")) { + CountsDictionary counts; + auto &measurements = job["measurementCounts"]; + + for (const auto &[bitstring, count] : measurements.items()) { + counts[bitstring] = count.get(); + } + + std::vector execResults; + execResults.emplace_back(ExecutionResult{counts}); + return cudaq::sample_result(execResults); + } + } + + // Last resort - check for direct measurementCounts in the response + if (getJobResponse.contains("measurementCounts")) { + CountsDictionary counts; + auto &measurements = getJobResponse["measurementCounts"]; + + for (const auto &[bitstring, count] : measurements.items()) { + counts[bitstring] = count.get(); + } + + std::vector execResults; + execResults.emplace_back(ExecutionResult{counts}); + return cudaq::sample_result(execResults); + } + + throw std::runtime_error("No measurement counts found in any response format"); + } + + /// @brief Override the polling interval method + std::chrono::microseconds + nextResultPollingInterval(ServerMessage &postResponse) override { + return std::chrono::seconds(1); + } + +private: + void saveResponseToFile(const ServerMessage &response, const std::string &identifier = "") { + try { + std::string outputDir = backendConfig.at("results_output_dir"); + std::string filePrefix = backendConfig.at("results_file_prefix"); + + // Create a unique filename using timestamp if no identifier is provided + std::string filename; + if (identifier.empty()) { + auto now = std::chrono::system_clock::now(); + auto timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); + filename = outputDir + "/" + filePrefix + std::to_string(timestamp) + ".json"; + } else { + filename = outputDir + "/" + filePrefix + identifier + ".json"; + } + + std::ofstream outputFile(filename); + if (!outputFile.is_open()) { + cudaq::info("Failed to open file for writing: {}", filename); + return; + } + + outputFile << response.dump(2); + outputFile.close(); + + cudaq::info("Response saved to file: {}", filename); + } catch (const std::exception &e) { + cudaq::info("Error saving response to file: {}", e.what()); + } + } + + RestHeaders getHeaders() override { + if (backendConfig.find("api_key") == backendConfig.end()) { + throw std::runtime_error("API key not found in config. Was initialize() called?"); + } + + RestHeaders headers; + headers["api-key"] = backendConfig.at("api_key"); + headers["Content-Type"] = "application/json"; + headers["User-Agent"] = backendConfig.at("user_agent"); + return headers; + } + + std::string getEnvVar(const std::string &key, const std::string &defaultVal, const bool isRequired) const { + const char *env_var = std::getenv(key.c_str()); + if (env_var == nullptr) { + if (isRequired) { + throw std::runtime_error(key + " environment variable is not set."); + } + + return defaultVal; + } + return std::string(env_var); + } + + std::string getValueOrDefault(const BackendConfig &config, + const std::string &key, + const std::string &defaultValue) const { + return config.find(key) != config.end() ? config.at(key) : defaultValue; + } +}; +} // namespace cudaq + +CUDAQ_REGISTER_TYPE(cudaq::ServerHelper, cudaq::QbraidServerHelper, qbraid) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml new file mode 100644 index 00000000000..5132a74d1a7 --- /dev/null +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml @@ -0,0 +1,30 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +name: qbraid +description: "CUDA-Q target for qBraid." +config: + # Tell DefaultQuantumPlatform what QPU subtype to use + platform-qpu: remote_rest + # Tell NVQ++ to generate glue code to set the target backend name + gen-target-backend: true + # Add the rest-qpu library to the link list + link-libs: ["-lcudaq-rest-qpu"] + # Define the lowering pipeline + platform-lowering-config: "classical-optimization-pipeline,globalize-array-values,func.func(state-prep),unitary-synthesis,canonicalize,apply-op-specialization,aggressive-early-inlining,classical-optimization-pipeline,func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),decomposition{enable-patterns=SToR1,TToR1,CCZToCX,CRyToCX,CRxToCX,R1AdjToR1,RxAdjToRx,RyAdjToRy,RzAdjToRz},quake-to-cc-prep,func.func(memtoreg{quantum=0}),symbol-dce" + # Tell the rest-qpu that we are generating OpenQASM. + codegen-emission: qasm2 + # Library mode is only for simulators, physical backends must turn this off + library-mode: false + +target-arguments: + - key: machine + required: false + type: string + platform-arg: qpu + help-string: "Specify the qBraid QPU." \ No newline at end of file diff --git a/targettests/execution/cudaq_observe-cpp17.cpp b/targettests/execution/cudaq_observe-cpp17.cpp new file mode 100644 index 00000000000..ffd05d7780f --- /dev/null +++ b/targettests/execution/cudaq_observe-cpp17.cpp @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// REQUIRES: c++17 +// clang-format off +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// 2 different IQM machines for 2 different topologies +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// clang-format on + +#include +#include + +// The example here shows a simple use case for the `cudaq::observe` +// function in computing expected values of provided spin_ops. + +struct ansatz { + auto operator()(double theta) __qpu__ { + cudaq::qvector q(2); + x(q[0]); + ry(theta, q[1]); + cx(q[1], q[0]); + } +}; + +int main() { + + // Build up your spin op algebraically + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + + // Make repeatable for shots-based emulation + cudaq::set_random_seed(13); + + // Observe takes the kernel, the spin_op, and the concrete + // parameters for the kernel + double energy = cudaq::observe(ansatz{}, h, .59); + printf("Energy is %.16lf\n", energy); + return 0; +} + +// Note: seeds 2 and 12 will push this to -2 instead of -1. All all other +// seeds in 1-100 range will be -1.x. + +// CHECK: Energy is -1. diff --git a/targettests/qbraid/bug_qubit.cpp b/targettests/qbraid/bug_qubit.cpp new file mode 100644 index 00000000000..2179c9f4da1 --- /dev/null +++ b/targettests/qbraid/bug_qubit.cpp @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// This code is from Issue 251. + +// clang-format off +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t +// RUN: cudaq-quake %cpp_std %s | cudaq-opt --promote-qubit-allocation | FileCheck --check-prefixes=MLIR %s + +#include +#include + +struct simple_x { + void operator()() __qpu__ { + cudaq::qubit q; + x(q); + mz(q); + } +}; + +// MLIR-LABEL: func.func @__nvqpp__mlirgen__simple_x() +// MLIR-NOT: quake.alloca !quake.ref +// MLIR: %[[VAL_0:.*]] = quake.alloca !quake.veq<1> +// MLIR-NEXT: %[[VAL_1:.*]] = quake.extract_ref %[[VAL_0]][0] : (!quake.veq<1>) -> !quake.ref + +int main() { + auto result = cudaq::sample(simple_x{}); + +#ifndef SYNTAX_CHECK + std::cout << result.most_probable() << '\n'; + assert("1" == result.most_probable()); +#endif + + return 0; +} + +// CHECK: 1 diff --git a/targettests/qbraid/callable_kernel_arg.cpp b/targettests/qbraid/callable_kernel_arg.cpp new file mode 100644 index 00000000000..759469537e7 --- /dev/null +++ b/targettests/qbraid/callable_kernel_arg.cpp @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// clang-format off +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t +// clang-format on + +#include +#include + +__qpu__ void bar(cudaq::qubit &q) { x(q); } + +struct baz { + __qpu__ void operator()(cudaq::qubit &q) { x(q); } +}; + +struct foo { + template + __qpu__ void operator()(CallableKernel &&func, int size) { + cudaq::qvector q(size); + func(q[0]); + auto result = mz(q[0]); + } +}; + +int main() { + auto result = cudaq::sample(1000, foo{}, baz{}, /*qreg size*/ 1); + +#ifndef SYNTAX_CHECK + std::cout << result.most_probable() << '\n'; + assert("1" == result.most_probable()); +#endif + + return 0; +} + +// CHECK: 1 diff --git a/targettests/qbraid/cudaq_observe.cpp b/targettests/qbraid/cudaq_observe.cpp new file mode 100644 index 00000000000..d9d1c537d85 --- /dev/null +++ b/targettests/qbraid/cudaq_observe.cpp @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// REQUIRES: c++20 +// clang-format off +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s +// 2 different IQM machines for 2 different topologies +// RUN: nvq++ --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// clang-format on + +#include +#include + +// The example here shows a simple use case for the `cudaq::observe` +// function in computing expected values of provided spin_ops. + +struct ansatz { + auto operator()(double theta) __qpu__ { + cudaq::qvector q(2); + x(q[0]); + ry(theta, q[1]); + x(q[1], q[0]); + } +}; + +int main() { + + // Build up your spin op algebraically + using namespace cudaq::spin; + cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + + .21829 * z(0) - 6.125 * z(1); + + // Make repeatable for shots-based emulation + cudaq::set_random_seed(13); + + // Observe takes the kernel, the spin_op, and the concrete + // parameters for the kernel + double energy = cudaq::observe(ansatz{}, h, .59); + printf("Energy is %.16lf\n", energy); + return 0; +} + +// Note: seeds 2 and 12 will push this to -2 instead of -1. All other seeds in +// 1-100 range will be -1.x. + +// CHECK: Energy is -1. diff --git a/targettests/qbraid/if_jit.cpp b/targettests/qbraid/if_jit.cpp new file mode 100644 index 00000000000..5719dc5b770 --- /dev/null +++ b/targettests/qbraid/if_jit.cpp @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// This code is from Issue 296. + +// clang-format off +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t +// clang-format on + +#include +#include + +__qpu__ void foo(bool value) { + cudaq::qubit q; + if (value) + x(q); + + mz(q); +} + +int main() { + auto result = cudaq::sample(100, foo, true); + +#ifndef SYNTAX_CHECK + std::cout << result.most_probable() << '\n'; + assert("1" == result.most_probable()); +#endif + + return 0; +} + +// CHECK: 1 diff --git a/targettests/qbraid/load_value.cpp b/targettests/qbraid/load_value.cpp new file mode 100644 index 00000000000..ab5d9cec62e --- /dev/null +++ b/targettests/qbraid/load_value.cpp @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// clang-format off +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t +// clang-format on + +#include +#include + +__qpu__ void load_value(unsigned value) { + cudaq::qvector qubits(4); + for (std::size_t i = 0; i < 4; ++i) { + // Doesn't work, even with: `if (value)` + if (value & (1 << i)) + x(qubits[3 - i]); + } + + mz(qubits); +} + +int main() { + for (auto i = 0; i < 16; ++i) { + auto result = cudaq::sample(1000, load_value, i); + +#ifndef SYNTAX_CHECK + std::cout << result.most_probable() << '\n'; + assert(i == std::stoi(result.most_probable(), nullptr, 2)); +#endif + } + return 0; +} + +// CHECK: 0000 +// CHECK-NEXT: 0001 +// CHECK-NEXT: 0010 +// CHECK-NEXT: 0011 +// CHECK-NEXT: 0100 +// CHECK-NEXT: 0101 +// CHECK-NEXT: 0110 +// CHECK-NEXT: 0111 +// CHECK-NEXT: 1000 +// CHECK-NEXT: 1001 +// CHECK-NEXT: 1010 +// CHECK-NEXT: 1011 +// CHECK-NEXT: 1100 +// CHECK-NEXT: 1101 +// CHECK-NEXT: 1110 +// CHECK-NEXT: 1111 diff --git a/targettests/qbraid/sudoku_2x2-1.cpp b/targettests/qbraid/sudoku_2x2-1.cpp new file mode 100644 index 00000000000..cd028025a0c --- /dev/null +++ b/targettests/qbraid/sudoku_2x2-1.cpp @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// REQUIRES: c++20 +// clang-format off +// RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// clang-format on + +#include +#include +#include +#include + +__qpu__ void reflect_uniform(cudaq::qvector<> &qubits) { + h(qubits); + x(qubits); + z(qubits[0], qubits[1], qubits[2], qubits[3]); + x(qubits); + h(qubits); +} + +__qpu__ void oracle(cudaq::qvector<> &cs, cudaq::qubit &target) { + x(cs[0], !cs[1], !cs[2], cs[3], target); + x(!cs[0], cs[1], cs[2], !cs[3], target); +} + +__qpu__ void grover() { + cudaq::qvector qubits(4); + cudaq::qubit ancilla; + + // Initialization + x(ancilla); + h(ancilla); + h(qubits); // uniform initialization + + // Don't work?: + for (int i = 0; i < 2; ++i) { + oracle(qubits, ancilla); + reflect_uniform(qubits); + } + + mz(qubits); +}; + +int main() { + auto result = cudaq::sample(1000, grover); + +#ifndef SYNTAX_CHECK + std::vector strings; + for (auto &&[bits, count] : result) { + strings.push_back(bits); + } + std::sort(strings.begin(), strings.end(), [&](auto &a, auto &b) { + return result.count(a) > result.count(b); + }); + std::cout << strings[0] << '\n'; + std::cout << strings[1] << '\n'; + + std::unordered_set most_probable{strings[0], strings[1]}; + assert(most_probable.count("1001") == 1); + assert(most_probable.count("0110") == 1); +#endif + + return 0; +} + +// CHECK-DAG: 1001 +// CHECK-DAG: 0110 diff --git a/targettests/qbraid/sudoku_2x2-bit_names.cpp b/targettests/qbraid/sudoku_2x2-bit_names.cpp new file mode 100644 index 00000000000..ef53021b359 --- /dev/null +++ b/targettests/qbraid/sudoku_2x2-bit_names.cpp @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// REQUIRES: c++20 +// clang-format off +// RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// clang-format on + +#include +#include +#include +#include + +__qpu__ void reflect_uniform(cudaq::qvector<> &qubits) { + h(qubits); + x(qubits); + z(qubits[0], qubits[1], qubits[2], qubits[3]); + x(qubits); + h(qubits); +} + +__qpu__ void oracle(cudaq::qvector<> &cs, cudaq::qubit &target) { + x(cs[0], !cs[1], !cs[2], cs[3], target); + x(!cs[0], cs[1], cs[2], !cs[3], target); +} + +__qpu__ void grover() { + cudaq::qvector qubits(4); + cudaq::qubit ancilla; + + // Initialization + x(ancilla); + h(ancilla); + h(qubits); // uniform initialization + + oracle(qubits, ancilla); + reflect_uniform(qubits); + oracle(qubits, ancilla); + reflect_uniform(qubits); + + auto groverQubits0 = mz(qubits[0]); + auto groverQubits1 = mz(qubits[1]); + auto groverQubits2 = mz(qubits[2]); + auto groverQubits3 = mz(qubits[3]); +}; + +int main() { + auto result = cudaq::sample(1000, grover); + result.dump(); + + auto& platform = cudaq::get_platform(); + if (platform.is_remote() || platform.is_emulated()) { + // Make sure that the get_marginal() results for the individual register names + // match the subset of the bits from the global register. + // Note that this will fail if you only compile this in library mode. + auto numBits = result.begin()->first.size(); + std::cout << "Checking " << numBits << " bits against global register\n"; + for (size_t b = 0; b < numBits; b++) { + auto regName = "groverQubits" + std::to_string(b); + auto valFromRegName = result.get_marginal({0}, regName); + auto valFromGlobal = result.get_marginal({b}); + if (valFromRegName.to_map() != valFromGlobal.to_map()) { + std::cout << "--- MISMATCH DETECTED in bit " << b << " ---\n"; + valFromRegName.dump(); + valFromGlobal.dump(); + // Mark test failure + assert(valFromRegName.to_map() == valFromGlobal.to_map()); + } + } + } + +#ifndef SYNTAX_CHECK + std::vector strings; + for (auto &&[bits, count] : result) { + strings.push_back(bits); + } + std::sort(strings.begin(), strings.end(), [&](auto& a, auto& b) { + return result.count(a) > result.count(b); + }); + std::cout << strings[0] << '\n'; + std::cout << strings[1] << '\n'; + + std::unordered_set most_probable{strings[0], strings[1]}; + assert(most_probable.count("1001") == 1); + assert(most_probable.count("0110") == 1); +#endif + + return 0; +} + +// CHECK-DAG: 1001 +// CHECK-DAG: 0110 diff --git a/targettests/qbraid/sudoku_2x2-reg_name.cpp b/targettests/qbraid/sudoku_2x2-reg_name.cpp new file mode 100644 index 00000000000..6200c1070f7 --- /dev/null +++ b/targettests/qbraid/sudoku_2x2-reg_name.cpp @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// REQUIRES: c++20 +// clang-format off +// RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// clang-format on + +#include +#include +#include +#include + +__qpu__ void reflect_uniform(cudaq::qvector<> &qubits) { + h(qubits); + x(qubits); + z(qubits[0], qubits[1], qubits[2], qubits[3]); + x(qubits); + h(qubits); +} + +__qpu__ void oracle(cudaq::qvector<> &cs, cudaq::qubit &target) { + x(cs[0], !cs[1], !cs[2], cs[3], target); + x(!cs[0], cs[1], cs[2], !cs[3], target); +} + +__qpu__ void grover() { + cudaq::qvector qubits(4); + cudaq::qubit ancilla; + + // Initialization + x(ancilla); + h(ancilla); + h(qubits); // uniform initialization + + oracle(qubits, ancilla); + reflect_uniform(qubits); + oracle(qubits, ancilla); + reflect_uniform(qubits); + + auto groverQubits = mz(qubits); +}; + +int main() { + auto result = cudaq::sample(1000, grover); + result.dump(); + +#ifndef SYNTAX_CHECK + std::vector strings; + for (auto &&[bits, count] : result) { + strings.push_back(bits); + } + std::sort(strings.begin(), strings.end(), [&](auto& a, auto& b) { + return result.count(a) > result.count(b); + }); + std::cout << strings[0] << '\n'; + std::cout << strings[1] << '\n'; + + std::unordered_set most_probable{strings[0], strings[1]}; + assert(most_probable.count("1001") == 1); + assert(most_probable.count("0110") == 1); +#endif + + return 0; +} + +// CHECK-DAG: 1001 +// CHECK-DAG: 0110 diff --git a/targettests/qbraid/sudoku_2x2.cpp b/targettests/qbraid/sudoku_2x2.cpp new file mode 100644 index 00000000000..e3d4bc2c0c3 --- /dev/null +++ b/targettests/qbraid/sudoku_2x2.cpp @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// REQUIRES: c++20 +// clang-format off +// RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// clang-format on + +#include +#include +#include +#include + +__qpu__ void reflect_uniform(cudaq::qvector<> &qubits) { + h(qubits); + x(qubits); + z(qubits[0], qubits[1], qubits[2], qubits[3]); + x(qubits); + h(qubits); +} + +__qpu__ void oracle(cudaq::qvector<> &cs, cudaq::qubit &target) { + x(cs[0], !cs[1], !cs[2], cs[3], target); + x(!cs[0], cs[1], cs[2], !cs[3], target); +} + +__qpu__ void grover() { + cudaq::qvector qubits(4); + cudaq::qubit ancilla; + + // Initialization + x(ancilla); + h(ancilla); + h(qubits); // uniform initialization + + oracle(qubits, ancilla); + reflect_uniform(qubits); + oracle(qubits, ancilla); + reflect_uniform(qubits); + + mz(qubits); +}; + +int main() { + auto result = cudaq::sample(1000, grover); + +#ifndef SYNTAX_CHECK + std::vector strings; + for (auto &&[bits, count] : result) { + strings.push_back(bits); + } + std::sort(strings.begin(), strings.end(), [&](auto& a, auto& b) { + return result.count(a) > result.count(b); + }); + std::cout << strings[0] << '\n'; + std::cout << strings[1] << '\n'; + + std::unordered_set most_probable{strings[0], strings[1]}; + assert(most_probable.count("1001") == 1); + assert(most_probable.count("0110") == 1); +#endif + + return 0; +} + +// CHECK-DAG: 1001 +// CHECK-DAG: 0110 diff --git a/targettests/qbraid/swap_gate.cpp b/targettests/qbraid/swap_gate.cpp new file mode 100644 index 00000000000..4f37edae871 --- /dev/null +++ b/targettests/qbraid/swap_gate.cpp @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// clang-format off +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t && %t | FileCheck %s + +#include "cudaq.h" +#include + +int main() { + + auto swapKernel = []() __qpu__ { + cudaq::qvector q(2); + x(q[0]); + swap(q[0], q[1]); + + mz(q); + }; + + auto counts = cudaq::sample(swapKernel); + +#ifndef SYNTAX_CHECK + std::cout << counts.most_probable() << '\n'; + assert("01" == counts.most_probable()); +#endif + + return 0; +} + +// CHECK: 01 diff --git a/targettests/qbraid/test-int8_t.cpp b/targettests/qbraid/test-int8_t.cpp new file mode 100644 index 00000000000..7178f6c57bb --- /dev/null +++ b/targettests/qbraid/test-int8_t.cpp @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// clang-format off +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t +// clang-format on + +#include +#include + +struct variable_qreg { + __qpu__ void operator()(std::uint8_t value) { + cudaq::qvector qubits(value); + + mz(qubits); + } +}; + +int main() { + for (auto i = 1; i < 5; ++i) { + auto result = cudaq::sample(1000, variable_qreg{}, i); + +#ifndef SYNTAX_CHECK + std::cout << result.most_probable() << '\n'; + assert(std::string(i, '0') == result.most_probable()); +#endif + } + + return 0; +} + +// CHECK: 0 +// CHECK: 00 +// CHECK: 000 +// CHECK: 0000 diff --git a/targettests/qbraid/test-int8_t_free_func.cpp b/targettests/qbraid/test-int8_t_free_func.cpp new file mode 100644 index 00000000000..ca9db25ec6c --- /dev/null +++ b/targettests/qbraid/test-int8_t_free_func.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// clang-format off +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t +// clang-format on + +#include +#include + +__qpu__ void variable_qreg(std::uint8_t value) { + cudaq::qvector qubits(value); + + mz(qubits); +} + +int main() { + for (auto i = 1; i < 5; ++i) { + auto result = cudaq::sample(1000, variable_qreg, i); + +#ifndef SYNTAX_CHECK + std::cout << result.most_probable() << '\n'; + assert(std::string(i, '0') == result.most_probable()); +#endif + } + + return 0; +} + +// CHECK: 0 +// CHECK-NEXT: 00 +// CHECK-NEXT: 000 +// CHECK-NEXT: 0000 diff --git a/targettests/qbraid/variable_size_qreg.cpp b/targettests/qbraid/variable_size_qreg.cpp new file mode 100644 index 00000000000..1f6c139a085 --- /dev/null +++ b/targettests/qbraid/variable_size_qreg.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// clang-format off +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t +// clang-format on + +#include +#include + +__qpu__ void variable_qreg(unsigned value) { + cudaq::qvector qubits(value); + + mz(qubits); +} + +int main() { + for (auto i = 1; i < 5; ++i) { + auto result = cudaq::sample(1000, variable_qreg, i); + +#ifndef SYNTAX_CHECK + std::cout << result.most_probable() << '\n'; + assert(std::string(i, '0') == result.most_probable()); +#endif + } + + return 0; +} + +// CHECK: 0 +// CHECK-NEXT: 00 +// CHECK-NEXT: 000 +// CHECK-NEXT: 0000 diff --git a/tpls/Stim b/tpls/Stim index 42e0b9e0991..47190f4a3af 160000 --- a/tpls/Stim +++ b/tpls/Stim @@ -1 +1 @@ -Subproject commit 42e0b9e099180e8570407c33f87b4683cac00d81 +Subproject commit 47190f4a3afb104c9f0068d0be9fea87d2894a70 diff --git a/tpls/cpr b/tpls/cpr index d202b82fbcc..871ed52d350 160000 --- a/tpls/cpr +++ b/tpls/cpr @@ -1 +1 @@ -Subproject commit d202b82fbccf897604a18e035c09e1330dffd082 +Subproject commit 871ed52d350214a034f6ef8a3b8f51c5ce1bd400 diff --git a/tpls/fmt b/tpls/fmt index fc8d07cfe54..ba50c19e827 160000 --- a/tpls/fmt +++ b/tpls/fmt @@ -1 +1 @@ -Subproject commit fc8d07cfe54ba9f5019453dfdb112491246ee017 +Subproject commit ba50c19e827383bd5dacb74189fb4852c8dcbdae diff --git a/tpls/spdlog b/tpls/spdlog index 287333ee005..edc51df1bda 160000 --- a/tpls/spdlog +++ b/tpls/spdlog @@ -1 +1 @@ -Subproject commit 287333ee00555aaece5a5cf6acc9040563c6f642 +Subproject commit edc51df1bdad8667b628999394a1e7c4dc6f3658 diff --git a/unittests/backends/CMakeLists.txt b/unittests/backends/CMakeLists.txt index ed42c11cc55..627ae6a7395 100644 --- a/unittests/backends/CMakeLists.txt +++ b/unittests/backends/CMakeLists.txt @@ -8,15 +8,15 @@ # List of libraries to link with by default to create a test executable set(default_backend_unittest_libs - fmt::fmt-header-only - cudaq-common + fmt::fmt-header-only + cudaq-common cudaq cudaq-builder cudaq-mlir-runtime cudaq-rest-qpu cudaq-operator nvqir nvqir-qpp - cudaq-platform-default + cudaq-platform-default gtest_main) define_property(DIRECTORY PROPERTY BACKEND_UNITTEST_LIBS INHERITED @@ -32,12 +32,12 @@ set_property(DIRECTORY PROPERTY BACKEND_UNITTEST_LIBS ${default_backend_unittest # Helper function to create an executable to be used by the gtest unit tests # - target: positional argument, name of the executable # - BACKEND: named argument to specify a prefix for the test names -# - BACKEND_CONFIG: if present, the test will set NVQPP_TARGET_BACKEND_CONFIG +# - BACKEND_CONFIG: if present, the test will set NVQPP_TARGET_BACKEND_CONFIG # with this value so the backend gets loaded by a constructor before entering main. # To avoid issues with semicolon the format is: backend key1=value1 key2=value2 # The function will convert this to : backend;key1;value1;key2;value2 # Example: infleqtion emulate=false url=http://localhost:62447 -# - LINK_LIBS: optional argument to provide non-default list of libraries to link with +# - LINK_LIBS: optional argument to provide non-default list of libraries to link with function(add_backend_unittest_executable target) set(singleValues BACKEND BACKEND_CONFIG) set(multiValues SOURCES INCLUDES LINK_LIBS) @@ -97,6 +97,9 @@ if (OPENSSL_FOUND AND CUDAQ_ENABLE_PYTHON AND CUDAQ_TEST_MOCK_SERVERS) if (CUDAQ_ENABLE_SCALEWAY_BACKEND) add_subdirectory(scaleway) endif() + if (CUDAQ_ENABLE_QBRAID_BACKEND) + add_subdirectory(qbraid) + endif() add_subdirectory(extra_payload_provider) add_subdirectory(quake_backend) endif() diff --git a/unittests/backends/qbraid/CMakeLists.txt b/unittests/backends/qbraid/CMakeLists.txt new file mode 100644 index 00000000000..05ca3c19550 --- /dev/null +++ b/unittests/backends/qbraid/CMakeLists.txt @@ -0,0 +1,27 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +add_executable(test_qbraid QbraidTester.cpp) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) + target_link_options(test_qbraid PRIVATE -Wl,--no-as-needed) +endif() +target_compile_definitions(test_qbraid PRIVATE -DNVQIR_BACKEND_NAME=qbraid) +target_include_directories(test_qbraid PRIVATE ../..) +target_link_libraries(test_qbraid + PRIVATE fmt::fmt-header-only + cudaq-common + cudaq + cudaq-builder + cudaq-mlir-runtime + cudaq-rest-qpu + cudaq-platform-default + gtest_main) + + +configure_file("QbraidStartServerAndTest.sh.in" "${CMAKE_BINARY_DIR}/unittests/backends/qbraid/QbraidStartServerAndTest.sh" @ONLY) +add_test(NAME qbraid-tests COMMAND bash QbraidStartServerAndTest.sh WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/unittests/backends/qbraid/) diff --git a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in new file mode 100644 index 00000000000..8ba8b822945 --- /dev/null +++ b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in @@ -0,0 +1,43 @@ +#!/bin/bash + +# ============================================================================ # +# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +checkServerConnection() { + PYTHONPATH=@CMAKE_BINARY_DIR@/python @Python_EXECUTABLE@ - << EOF +import socket +try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(("localhost", 62449)) + s.close() +except Exception: + exit(1) +EOF +} + +# Launch the fake server +PYTHONPATH=@CMAKE_BINARY_DIR@/python @Python_EXECUTABLE@ @CMAKE_SOURCE_DIR@/utils/mock_qpu/qbraid/__init__.py & +# we'll need the process id to kill it +pid=$(echo "$!") +n=0 +while ! checkServerConnection; do + sleep 1 + n=$((n+1)) + if [ "$n" -eq "10" ]; then + kill -INT $pid + exit 99 + fi +done +# Run the tests +./test_qbraid +# Did they fail? +testsPassed=$? +# kill the server +kill -INT $pid +# return success / failure +exit $testsPassed diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp new file mode 100644 index 00000000000..9046199e798 --- /dev/null +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "CUDAQTestUtils.h" +#include "common/FmtCore.h" +#include "cudaq/algorithm.h" +#include +#include +#include + +// Update the backend string to match the QBraid format +std::string mockPort = "62449"; +std::string backendStringTemplate = + "qbraid;emulate;false;url;http://localhost:{}"; + +bool isValidExpVal(double value) { + // give us some wiggle room while keep the tests fast + return value < -1.1 && value > -2.3; +} + +CUDAQ_TEST(QbraidTester, checkSampleSync) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + auto counts = cudaq::sample(kernel); + counts.dump(); + EXPECT_EQ(counts.size(), 2); +} + +CUDAQ_TEST(QbraidTester, checkSampleAsync) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + auto future = cudaq::sample_async(kernel); + auto counts = future.get(); + EXPECT_EQ(counts.size(), 2); +} + +CUDAQ_TEST(QbraidTester, checkSampleAsyncLoadFromFile) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + auto future = cudaq::sample_async(kernel); + { + std::ofstream out("saveMe.json"); + out << future; + } + + cudaq::async_result readIn; + std::ifstream in("saveMe.json"); + in >> readIn; + + auto counts = readIn.get(); + EXPECT_EQ(counts.size(), 2); + + std::remove("saveMe.json"); +} + +CUDAQ_TEST(QbraidTester, checkObserveSync) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto [kernel, theta] = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.x(qubit[0]); + kernel.ry(theta, qubit[1]); + kernel.x(qubit[1], qubit[0]); + + using namespace cudaq::spin; + cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + + .21829 * z(0) - 6.125 * z(1); + auto result = cudaq::observe(kernel, h, .59); + result.dump(); + + printf("ENERGY: %lf\n", result.expectation()); + EXPECT_TRUE(isValidExpVal(result.expectation())); +} + +CUDAQ_TEST(QbraidTester, checkObserveAsync) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto [kernel, theta] = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.x(qubit[0]); + kernel.ry(theta, qubit[1]); + kernel.x(qubit[1], qubit[0]); + + using namespace cudaq::spin; + cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + + .21829 * z(0) - 6.125 * z(1); + auto future = cudaq::observe_async(kernel, h, .59); + + auto result = future.get(); + result.dump(); + + printf("ENERGY: %lf\n", result.expectation()); + EXPECT_TRUE(isValidExpVal(result.expectation())); +} + +CUDAQ_TEST(QbraidTester, checkObserveAsyncLoadFromFile) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto [kernel, theta] = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.x(qubit[0]); + kernel.ry(theta, qubit[1]); + kernel.x(qubit[1], qubit[0]); + + using namespace cudaq::spin; + cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + + .21829 * z(0) - 6.125 * z(1); + auto future = cudaq::observe_async(kernel, h, .59); + + { + std::ofstream out("saveMeObserve.json"); + out << future; + } + + cudaq::async_result readIn(&h); + std::ifstream in("saveMeObserve.json"); + in >> readIn; + + auto result = readIn.get(); + + std::remove("saveMeObserve.json"); + result.dump(); + + printf("ENERGY: %lf\n", result.expectation()); + EXPECT_TRUE(isValidExpVal(result.expectation())); +} + +int main(int argc, char **argv) { + setenv("QBRAID_API_KEY", "00000000000000000000000000000000", 0); + ::testing::InitGoogleTest(&argc, argv); + auto ret = RUN_ALL_TESTS(); + return ret; +} \ No newline at end of file diff --git a/utils/mock_qpu/__init__.py b/utils/mock_qpu/__init__.py index 8167902c1e1..c508a32c796 100644 --- a/utils/mock_qpu/__init__.py +++ b/utils/mock_qpu/__init__.py @@ -21,6 +21,7 @@ "qci": 62449, "scaleway": 62450, "tii": 62451, + "qbraid": 62452, } diff --git a/utils/mock_qpu/qbraid/__init__.py b/utils/mock_qpu/qbraid/__init__.py new file mode 100644 index 00000000000..9d5bae322c1 --- /dev/null +++ b/utils/mock_qpu/qbraid/__init__.py @@ -0,0 +1,240 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +import itertools +import random +import re +import uuid +from typing import Any, Optional + +import uvicorn +from fastapi import FastAPI, Header, HTTPException, Query +from pydantic import BaseModel + +app = FastAPI() + + +class Job(BaseModel): + """Data required to submit a quantum job.""" + + openQasm: str + shots: int + qbraidDeviceId: str + + +JOBS_MOCK_DB = {} +JOBS_MOCK_RESULTS = {} + + +def count_qubits(qasm: str) -> int: + """Extracts the number of qubits from an OpenQASM string.""" + pattern = r"qreg\s+\w+\[(\d+)\];" + + match = re.search(pattern, qasm) + + if match: + return int(match.group(1)) + + raise ValueError("No qreg declaration found in the OpenQASM string.") + + +# def simulate_job(qasm: str, num_shots: int) -> dict[str, int]: +# """Simulates a quantum job by generating random measurement outcomes.""" +# num_qubits = count_qubits(qasm) + +# all_states = ["".join(p) for p in itertools.product("01", repeat=num_qubits)] +# num_states_to_select = random.randint(1, len(all_states)) +# selected_states = random.sample(all_states, num_states_to_select) +# distribution = random.choices(selected_states, k=num_shots) + +# result = {state: distribution.count(state) for state in selected_states} + +# return result + + +def simulate_job(qasm: str, num_shots: int) -> dict[str, int]: + """Simulates a quantum job by generating random measurement outcomes based on the circuit.""" + num_qubits = count_qubits(qasm) + + measured_qubits = [] + + measure_pattern = r"measure\s+(\w+)\[(\d+)\]" + measure_matches = re.findall(measure_pattern, qasm) + + hadamard_pattern = r"h\s+(\w+)\[(\d+)\]" + hadamard_matches = re.findall(hadamard_pattern, qasm) + + superposition_qubits = set() + for _, qubit_idx in hadamard_matches: + superposition_qubits.add(int(qubit_idx)) + + for _, qubit_idx in measure_matches: + measured_qubits.append(int(qubit_idx)) + + if not measured_qubits: + measured_qubits = list(range(num_qubits)) + + result = {} + + possible_states = [] + + if measured_qubits: + # Generate strings of the appropriate length for measured qubits + # For superposition qubits, include both 0 and 1 outcomes + for measured_qubit in measured_qubits: + if measured_qubit in superposition_qubits: + if not possible_states: + possible_states = ["0", "1"] + else: + new_states = [] + for state in possible_states: + new_states.append(state + "0") + new_states.append(state + "1") + possible_states = new_states + else: + if not possible_states: + possible_states = ["0"] + else: + possible_states = [state + "0" for state in possible_states] + + if not possible_states: + if superposition_qubits: + possible_states = ["0", "1"] + else: + possible_states = ["0" * num_qubits] + + distribution = random.choices(possible_states, k=num_shots) + result = {state: distribution.count(state) for state in set(distribution)} + + if ( + num_qubits == 2 + and len(measured_qubits) == 1 + and measured_qubits[0] == 0 + and 0 in superposition_qubits + ): + new_result = {} + total_shots = num_shots + half_shots = total_shots // 2 + + new_result["00"] = random.randint( + half_shots - half_shots // 4, half_shots + half_shots // 4 + ) + new_result["01"] = 0 + new_result["10"] = random.randint( + half_shots - half_shots // 4, half_shots + half_shots // 4 + ) + new_result["11"] = 0 + + remaining = total_shots - (new_result["00"] + new_result["10"]) + if remaining > 0: + new_result["00"] += remaining + + result = {k: v for k, v in new_result.items() if v > 0} + + return result + + +def poll_job_status(job_id: str) -> dict[str, Any]: + """Updates the status of a job and returns the updated job data.""" + if job_id not in JOBS_MOCK_DB: + raise HTTPException(status_code=404, detail="Job not found") + + status = JOBS_MOCK_DB[job_id]["status"] + + status_transitions = { + "INITIALIZING": "QUEUED", + "QUEUED": "RUNNING", + "RUNNING": "COMPLETED", + "CANCELLING": "CANCELLED", + } + + new_status = status_transitions.get(status, status) + JOBS_MOCK_DB[job_id]["status"] = new_status + + return {"qbraidJobId": job_id, **JOBS_MOCK_DB[job_id]} + + +@app.post("/quantum-jobs") +async def postJob(job: Job, api_key: Optional[str] = Header(None, alias="api-key")): + """Submit a quantum job for execution.""" + if api_key is None: + raise HTTPException(status_code=401, detail="API key is required") + + newId = str(uuid.uuid4()) + + counts = simulate_job(job.openQasm, job.shots) + + job_data = {"status": "INITIALIZING", "statusText": "", **job.model_dump()} + + JOBS_MOCK_DB[newId] = job_data + JOBS_MOCK_RESULTS[newId] = counts + + return {"qbraidJobId": newId, **job_data} + + +@app.get("/quantum-jobs") +async def getJobs( + job_id: Optional[str] = Query(None, alias="qbraidJobId"), + api_key: Optional[str] = Header(None, alias="api-key"), +): + """Retrieve the status of one or more quantum jobs.""" + if api_key is None: + raise HTTPException(status_code=401, detail="API key is required") + + jobs_array = [] + if job_id is None: + for job in JOBS_MOCK_DB: + job_data = poll_job_status(job) + jobs_array.append(job_data) + else: + job_data = poll_job_status(job_id) + jobs_array.append(job_data) + + res = {"jobsArray": jobs_array, "total": len(jobs_array)} + + return res + + +@app.get("/quantum-jobs/result/{job_id}") +async def getJobResult(job_id: str, api_key: Optional[str] = Header(None, alias="api-key")): + """Retrieve the results of a quantum job.""" + if api_key is None: + raise HTTPException(status_code=401, detail="API key is required") + + if job_id not in JOBS_MOCK_DB: + raise HTTPException(status_code=404, detail="Job not found") + + if JOBS_MOCK_DB[job_id]["status"] in {"FAILED", "CANCELLED"}: + raise HTTPException( + status_code=409, detail="Results unavailable. Job failed or was cancelled." + ) + + if JOBS_MOCK_DB[job_id]["status"] != "COMPLETED": + return { + "error": "Job still in progress. Results will be available once job is completed.", + "data": {}, + } + + if job_id not in JOBS_MOCK_RESULTS: + raise HTTPException(status_code=500, detail="Job results not found") + + if random.random() < 0.2: + return {"error": "Failed to retrieve job results. Please wait, and try again.", "data": {}} + + counts = JOBS_MOCK_RESULTS[job_id] + + return {"data": {"measurementCounts": counts}} + + +def startServer(port): + """Start the REST server.""" + uvicorn.run(app, port=port, host="0.0.0.0", log_level="info") + + +if __name__ == "__main__": + startServer(62449) From 925ae39eebd02886afd9415a9546b1f74fc65d15 Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Fri, 13 Mar 2026 13:27:56 +0530 Subject: [PATCH 02/18] update: migrate cudaq to platform v2 --- .../helpers/qbraid/QbraidServerHelper.cpp | 151 +++++++++--------- .../qbraid/QbraidStartServerAndTest.sh.in | 4 +- unittests/backends/qbraid/QbraidTester.cpp | 2 +- utils/mock_qpu/qbraid/__init__.py | 136 ++++++++++------ 4 files changed, 164 insertions(+), 129 deletions(-) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index 5e2bf74787e..5e930c0f2da 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -3,16 +3,13 @@ #include "common/ServerHelper.h" #include "cudaq/Support/Version.h" #include "cudaq/utils/cudaq_utils.h" -#include -#include -#include #include namespace cudaq { class QbraidServerHelper : public ServerHelper { - static constexpr const char *DEFAULT_URL = "https://api.qbraid.com/api"; - static constexpr const char *DEFAULT_DEVICE = "ionq_simulator"; + static constexpr const char *DEFAULT_URL = "https://api-v2.qbraid.com/api/v1"; + static constexpr const char *DEFAULT_DEVICE = "ionq:ionq:sim:simulator"; static constexpr int DEFAULT_QUBITS = 29; public: @@ -28,8 +25,7 @@ class QbraidServerHelper : public ServerHelper { backendConfig["qubits"] = std::to_string(DEFAULT_QUBITS); backendConfig["api_key"] = getEnvVar("QBRAID_API_KEY", "", true); - backendConfig["job_path"] = backendConfig["url"] + "/quantum-jobs"; - backendConfig["results_path"] = backendConfig["url"] + "/quantum-jobs/result/"; + backendConfig["job_path"] = backendConfig["url"] + "/jobs"; backendConfig["results_output_dir"] = getValueOrDefault(config, "results_output_dir", "./qbraid_results"); backendConfig["results_file_prefix"] = getValueOrDefault(config, "results_file_prefix", "qbraid_job_"); @@ -63,14 +59,18 @@ class QbraidServerHelper : public ServerHelper { std::vector jobs; for (auto &circuitCode : circuitCodes) { ServerMessage job; - job["qbraidDeviceId"] = backendConfig.at("device_id"); - job["openQasm"] = circuitCode.code; + job["deviceQrn"] = backendConfig.at("device_id"); job["shots"] = std::stoi(backendConfig.at("shots")); + // v2 API: program is a structured object with format and data + nlohmann::json program; + program["format"] = "qasm2"; + program["data"] = circuitCode.code; + job["program"] = program; + + // v2 API: name is a top-level field (not nested under tags) if (!circuitCode.name.empty()) { - nlohmann::json tags; - tags["name"] = circuitCode.name; - job["tags"] = tags; + job["name"] = circuitCode.name; } jobs.push_back(job); @@ -80,40 +80,47 @@ class QbraidServerHelper : public ServerHelper { } std::string extractJobId(ServerMessage &postResponse) override { - if (!postResponse.contains("qbraidJobId")) { - throw std::runtime_error("ServerMessage doesn't contain 'qbraidJobId' key."); + // v2 API: jobQrn is nested under data envelope + if (postResponse.contains("data") && postResponse["data"].contains("jobQrn")) { + return postResponse["data"]["jobQrn"].get(); } - return postResponse.at("qbraidJobId"); + throw std::runtime_error("ServerMessage doesn't contain 'data.jobQrn' key."); } std::string constructGetJobPath(ServerMessage &postResponse) override { - if (!postResponse.contains("qbraidJobId")) { - throw std::runtime_error("ServerMessage doesn't contain 'qbraidJobId' key."); + // v2 API: use path parameter instead of query parameter + if (postResponse.contains("data") && postResponse["data"].contains("jobQrn")) { + return backendConfig.at("job_path") + "/" + postResponse["data"]["jobQrn"].get(); } - - return backendConfig.at("job_path") + "?qbraidJobId=" + postResponse.at("qbraidJobId").get(); + throw std::runtime_error("ServerMessage doesn't contain 'data.jobQrn' key."); } std::string constructGetJobPath(std::string &jobId) override { - return backendConfig.at("job_path") + "?qbraidJobId=" + jobId; + // v2 API: /jobs/{jobQrn} + return backendConfig.at("job_path") + "/" + jobId; } std::string constructGetResultsPath(const std::string &jobId) { - return backendConfig.at("results_path") + jobId; + // v2 API: /jobs/{jobQrn}/result + return backendConfig.at("job_path") + "/" + jobId + "/result"; + } + + std::string constructGetProgramPath(const std::string &jobId) { + // v2 API: /jobs/{jobQrn}/program + return backendConfig.at("job_path") + "/" + jobId + "/program"; } bool jobIsDone(ServerMessage &getJobResponse) override { std::string status; - if (getJobResponse.contains("jobsArray") && !getJobResponse["jobsArray"].empty()) { - status = getJobResponse["jobsArray"][0]["status"].get(); - cudaq::info("Job status from jobs endpoint: {}", status); + // v2 API: status is nested under data envelope + if (getJobResponse.contains("data") && getJobResponse["data"].contains("status")) { + status = getJobResponse["data"]["status"].get(); + cudaq::info("Job status from v2 data envelope: {}", status); } else if (getJobResponse.contains("status")) { + // Fallback: direct status field status = getJobResponse["status"].get(); cudaq::info("Job status from direct response: {}", status); - } else if (getJobResponse.contains("data") && getJobResponse["data"].contains("status")) { - status = getJobResponse["data"]["status"].get(); - cudaq::info("Job status from data object: {}", status); } else { cudaq::info("Unexpected job response format: {}", getJobResponse.dump()); throw std::runtime_error("Invalid job response format"); @@ -127,7 +134,26 @@ class QbraidServerHelper : public ServerHelper { return false; } - // Sample results with results api - with retry logic + // Fetch the original program from v2 endpoint + std::string getJobProgram(const ServerMessage &response, const std::string &jobId) override { + auto programPath = constructGetProgramPath(jobId); + auto headers = getHeaders(); + + cudaq::info("Fetching job program from v2 endpoint: {}", programPath); + RestClient client; + auto programJson = client.get("", programPath, headers, true); + + // v2 API: program content at data.data, format at data.format + if (programJson.contains("data") && programJson["data"].contains("data")) { + cudaq::info("Retrieved program (format: {})", + programJson["data"].value("format", "unknown")); + return programJson["data"]["data"].get(); + } + + throw std::runtime_error("Invalid program response format: " + programJson.dump()); + } + + // Fetch results from v2 results endpoint with retry logic cudaq::sample_result processResults(ServerMessage &getJobResponse, std::string &jobId) override { int maxRetries = 5; int waitTime = 2; @@ -138,23 +164,30 @@ class QbraidServerHelper : public ServerHelper { auto resultsPath = constructGetResultsPath(jobId); auto headers = getHeaders(); - cudaq::info("Fetching results using direct endpoint (attempt {}/{}): {}", attempt + 1, maxRetries, resultsPath); + cudaq::info("Fetching results from v2 endpoint (attempt {}/{}): {}", attempt + 1, maxRetries, resultsPath); RestClient client; auto resultJson = client.get("", resultsPath, headers, true); - if (resultJson.contains("error") && !resultJson["error"].is_null()) { - std::string errorMsg = resultJson["error"].is_string() - ? resultJson["error"].get() - : resultJson["error"].dump(); - cudaq::info("Error from results endpoint: {}", errorMsg); + // v2 API: error indicated by success=false + if (resultJson.contains("success") && resultJson["success"].is_boolean() + && !resultJson["success"].get()) { + std::string errorMsg = "Results not yet available"; + if (resultJson.contains("data") && resultJson["data"].contains("message")) { + errorMsg = resultJson["data"]["message"].get(); + } + cudaq::info("Results endpoint returned success=false: {}", errorMsg); if (attempt == maxRetries - 1) { throw std::runtime_error("Error retrieving results: " + errorMsg); } - } else if (resultJson.contains("data") && resultJson["data"].contains("measurementCounts")) { - cudaq::info("Processing results from direct endpoint"); + } + // v2 API: measurementCounts nested under data.resultData + else if (resultJson.contains("data") + && resultJson["data"].contains("resultData") + && resultJson["data"]["resultData"].contains("measurementCounts")) { + cudaq::info("Processing results from v2 endpoint"); CountsDictionary counts; - auto &measurements = resultJson["data"]["measurementCounts"]; + auto &measurements = resultJson["data"]["resultData"]["measurementCounts"]; for (const auto &[bitstring, count] : measurements.items()) { counts[bitstring] = @@ -168,7 +201,7 @@ class QbraidServerHelper : public ServerHelper { return cudaq::sample_result(execResults); } - // If we get here, no valid data was found but also no error - retry + // No valid data yet and no explicit error - retry if (attempt < maxRetries - 1) { int sleepTime = (attempt == 0) ? waitTime : waitTime * std::pow(backoffFactor, attempt); cudaq::info("No valid results yet, retrying in {} seconds", sleepTime); @@ -176,51 +209,17 @@ class QbraidServerHelper : public ServerHelper { } } catch (const std::exception &e) { - cudaq::info("Exception when using direct results endpoint: {}", e.what()); + cudaq::info("Exception when fetching results: {}", e.what()); if (attempt < maxRetries - 1) { int sleepTime = (attempt == 0) ? waitTime : waitTime * std::pow(backoffFactor, attempt); cudaq::info("Retrying in {} seconds", sleepTime); std::this_thread::sleep_for(std::chrono::seconds(sleepTime)); - } else { - cudaq::info("Falling back to original results processing method"); } } } - // Original result processing as fallback - cudaq::info("Processing results from job response for job {}", jobId); - if (getJobResponse.contains("jobsArray") && !getJobResponse["jobsArray"].empty()) { - auto &job = getJobResponse["jobsArray"][0]; - - if (job.contains("measurementCounts")) { - CountsDictionary counts; - auto &measurements = job["measurementCounts"]; - - for (const auto &[bitstring, count] : measurements.items()) { - counts[bitstring] = count.get(); - } - - std::vector execResults; - execResults.emplace_back(ExecutionResult{counts}); - return cudaq::sample_result(execResults); - } - } - - // Last resort - check for direct measurementCounts in the response - if (getJobResponse.contains("measurementCounts")) { - CountsDictionary counts; - auto &measurements = getJobResponse["measurementCounts"]; - - for (const auto &[bitstring, count] : measurements.items()) { - counts[bitstring] = count.get(); - } - - std::vector execResults; - execResults.emplace_back(ExecutionResult{counts}); - return cudaq::sample_result(execResults); - } - - throw std::runtime_error("No measurement counts found in any response format"); + throw std::runtime_error("Failed to retrieve measurement counts after " + + std::to_string(maxRetries) + " attempts"); } /// @brief Override the polling interval method @@ -266,7 +265,7 @@ class QbraidServerHelper : public ServerHelper { } RestHeaders headers; - headers["api-key"] = backendConfig.at("api_key"); + headers["X-API-KEY"] = backendConfig.at("api_key"); headers["Content-Type"] = "application/json"; headers["User-Agent"] = backendConfig.at("user_agent"); return headers; diff --git a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in index 8ba8b822945..72ec44e9433 100644 --- a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in +++ b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in @@ -13,7 +13,7 @@ checkServerConnection() { import socket try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(("localhost", 62449)) + s.connect(("localhost", 62452)) s.close() except Exception: exit(1) @@ -35,7 +35,7 @@ while ! checkServerConnection; do done # Run the tests ./test_qbraid -# Did they fail? +# Did they fail? testsPassed=$? # kill the server kill -INT $pid diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp index 9046199e798..e3e94fb8087 100644 --- a/unittests/backends/qbraid/QbraidTester.cpp +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -14,7 +14,7 @@ #include // Update the backend string to match the QBraid format -std::string mockPort = "62449"; +std::string mockPort = "62452"; std::string backendStringTemplate = "qbraid;emulate;false;url;http://localhost:{}"; diff --git a/utils/mock_qpu/qbraid/__init__.py b/utils/mock_qpu/qbraid/__init__.py index 9d5bae322c1..1bb225a59f7 100644 --- a/utils/mock_qpu/qbraid/__init__.py +++ b/utils/mock_qpu/qbraid/__init__.py @@ -13,18 +13,27 @@ from typing import Any, Optional import uvicorn -from fastapi import FastAPI, Header, HTTPException, Query +from fastapi import FastAPI, Header, HTTPException, Path from pydantic import BaseModel app = FastAPI() +class Program(BaseModel): + """Structured program payload for v2 API.""" + + format: str + data: str + + class Job(BaseModel): - """Data required to submit a quantum job.""" + """Data required to submit a quantum job (v2 API).""" - openQasm: str + program: Program shots: int - qbraidDeviceId: str + deviceQrn: str + name: Optional[str] = None + tags: Optional[dict] = None JOBS_MOCK_DB = {} @@ -43,20 +52,6 @@ def count_qubits(qasm: str) -> int: raise ValueError("No qreg declaration found in the OpenQASM string.") -# def simulate_job(qasm: str, num_shots: int) -> dict[str, int]: -# """Simulates a quantum job by generating random measurement outcomes.""" -# num_qubits = count_qubits(qasm) - -# all_states = ["".join(p) for p in itertools.product("01", repeat=num_qubits)] -# num_states_to_select = random.randint(1, len(all_states)) -# selected_states = random.sample(all_states, num_states_to_select) -# distribution = random.choices(selected_states, k=num_shots) - -# result = {state: distribution.count(state) for state in selected_states} - -# return result - - def simulate_job(qasm: str, num_shots: int) -> dict[str, int]: """Simulates a quantum job by generating random measurement outcomes based on the circuit.""" num_qubits = count_qubits(qasm) @@ -156,54 +151,79 @@ def poll_job_status(job_id: str) -> dict[str, Any]: new_status = status_transitions.get(status, status) JOBS_MOCK_DB[job_id]["status"] = new_status - return {"qbraidJobId": job_id, **JOBS_MOCK_DB[job_id]} + return {"jobQrn": job_id, **JOBS_MOCK_DB[job_id]} -@app.post("/quantum-jobs") -async def postJob(job: Job, api_key: Optional[str] = Header(None, alias="api-key")): - """Submit a quantum job for execution.""" - if api_key is None: +# v2 API: POST /jobs +@app.post("/jobs") +async def postJob(job: Job, x_api_key: Optional[str] = Header(None, alias="X-API-KEY")): + """Submit a quantum job for execution (v2 API).""" + if x_api_key is None: raise HTTPException(status_code=401, detail="API key is required") newId = str(uuid.uuid4()) - counts = simulate_job(job.openQasm, job.shots) + # Extract QASM from the structured program payload + counts = simulate_job(job.program.data, job.shots) job_data = {"status": "INITIALIZING", "statusText": "", **job.model_dump()} JOBS_MOCK_DB[newId] = job_data JOBS_MOCK_RESULTS[newId] = counts - return {"qbraidJobId": newId, **job_data} + # v2 response: wrapped in success/data envelope + return {"success": True, "data": {"jobQrn": newId, "status": "INITIALIZING"}} -@app.get("/quantum-jobs") -async def getJobs( - job_id: Optional[str] = Query(None, alias="qbraidJobId"), - api_key: Optional[str] = Header(None, alias="api-key"), +# v2 API: GET /jobs/{job_qrn} +@app.get("/jobs/{job_id}") +async def getJob( + job_id: str = Path(...), + x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), ): - """Retrieve the status of one or more quantum jobs.""" - if api_key is None: + """Retrieve the status of a quantum job (v2 API).""" + if x_api_key is None: raise HTTPException(status_code=401, detail="API key is required") - jobs_array = [] - if job_id is None: - for job in JOBS_MOCK_DB: - job_data = poll_job_status(job) - jobs_array.append(job_data) - else: - job_data = poll_job_status(job_id) - jobs_array.append(job_data) + job_data = poll_job_status(job_id) - res = {"jobsArray": jobs_array, "total": len(jobs_array)} + # v2 response: wrapped in success/data envelope + return {"success": True, "data": job_data} - return res +# v2 API: GET /jobs/{job_qrn}/program +@app.get("/jobs/{job_id}/program") +async def getJobProgram( + job_id: str = Path(...), + x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), +): + """Retrieve the program of a quantum job (v2 API).""" + if x_api_key is None: + raise HTTPException(status_code=401, detail="API key is required") -@app.get("/quantum-jobs/result/{job_id}") -async def getJobResult(job_id: str, api_key: Optional[str] = Header(None, alias="api-key")): - """Retrieve the results of a quantum job.""" - if api_key is None: + if job_id not in JOBS_MOCK_DB: + raise HTTPException(status_code=404, detail="Job not found") + + job_data = JOBS_MOCK_DB[job_id] + + # Return the stored program in v2 format: { success, data: { format, data } } + return { + "success": True, + "data": { + "format": job_data.get("program", {}).get("format", "qasm2"), + "data": job_data.get("program", {}).get("data", ""), + }, + } + + +# v2 API: GET /jobs/{job_qrn}/result +@app.get("/jobs/{job_id}/result") +async def getJobResult( + job_id: str = Path(...), + x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), +): + """Retrieve the results of a quantum job (v2 API).""" + if x_api_key is None: raise HTTPException(status_code=401, detail="API key is required") if job_id not in JOBS_MOCK_DB: @@ -215,20 +235,36 @@ async def getJobResult(job_id: str, api_key: Optional[str] = Header(None, alias= ) if JOBS_MOCK_DB[job_id]["status"] != "COMPLETED": + # v2: use success=false instead of "error" field return { - "error": "Job still in progress. Results will be available once job is completed.", - "data": {}, + "success": False, + "data": {"status": JOBS_MOCK_DB[job_id]["status"]}, } if job_id not in JOBS_MOCK_RESULTS: raise HTTPException(status_code=500, detail="Job results not found") if random.random() < 0.2: - return {"error": "Failed to retrieve job results. Please wait, and try again.", "data": {}} + return { + "success": False, + "data": { + "status": "COMPLETED", + "message": "Failed to retrieve job results. Please wait, and try again.", + }, + } counts = JOBS_MOCK_RESULTS[job_id] - return {"data": {"measurementCounts": counts}} + # v2 response: measurementCounts nested under data.resultData + return { + "success": True, + "data": { + "resultData": {"measurementCounts": counts}, + "status": "COMPLETED", + "cost": 0, + "timeStamps": {}, + }, + } def startServer(port): @@ -237,4 +273,4 @@ def startServer(port): if __name__ == "__main__": - startServer(62449) + startServer(62452) From 41fe2486fc9777cd8941ec1d4f4df5da8a6cf389 Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Fri, 13 Mar 2026 13:34:02 +0530 Subject: [PATCH 03/18] fix: merge conflicts --- .github/workflows/integration_tests.yml | 30 +++++------ docs/sphinx/using/backends/cloud.rst | 7 +-- docs/sphinx/using/backends/cloud/qbraid.rst | 1 - .../using/backends/hardware/iontrap.rst | 50 +++++++++---------- 4 files changed, 37 insertions(+), 51 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index ee781d737b5..508ed712532 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -10,9 +10,9 @@ on: workflow_dispatch: inputs: target: - description: 'Target (choose nightly to run like nightly tests)' + description: "Target (choose nightly to run like nightly tests)" required: true - default: 'nightly' + default: "nightly" type: choice options: - nightly @@ -23,51 +23,47 @@ on: - iqm - oqc - orca -<<<<<<< HEAD - pasqal - qci - quantinuum - scaleway - tii -======= - - fermioniq - qbraid ->>>>>>> 17f25cf4 (qBraid integration MVP (#4)) single_test_name: type: string required: false - description: 'Single test (e.g., targettests/quantinuum/load_value.cpp). Runs default tests if left blank' + description: "Single test (e.g., targettests/quantinuum/load_value.cpp). Runs default tests if left blank" target_machine: type: string required: false - description: 'Target machine (e.g., H2-1E).' + description: "Target machine (e.g., H2-1E)." cudaq_test_image: type: string required: false - default: '' # picked up from repo variable if not provided - description: 'CUDA Quantum image to run the tests in. Default to the latest CUDA Quantum nightly image' + default: "" # picked up from repo variable if not provided + description: "CUDA Quantum image to run the tests in. Default to the latest CUDA Quantum nightly image" commit_sha: type: string required: false - description: 'Commit SHA to pull the code (examples/tests) for testing. Default to the commit associated with the CUDA Quantum docker image if left blank' + description: "Commit SHA to pull the code (examples/tests) for testing. Default to the commit associated with the CUDA Quantum docker image if left blank" workflow_id: type: string required: false - description: 'Workflow Id to retrieve the Python wheel for testing. Default to the wheels produced by the Publishing workflow associated with the latest nightly CUDA Quantum Docker image if left blank' + description: "Workflow Id to retrieve the Python wheel for testing. Default to the wheels produced by the Publishing workflow associated with the latest nightly CUDA Quantum Docker image if left blank" python_version: type: choice required: true - description: 'Python version to run wheel test' + description: "Python version to run wheel test" options: - - '3.11' - - '3.12' - - '3.13' + - "3.11" + - "3.12" + - "3.13" schedule: - cron: 0 3 * * * env: - python_version: '3.12' + python_version: "3.12" jobs: # Run a daily check of all links in the docs to find any newly broken links diff --git a/docs/sphinx/using/backends/cloud.rst b/docs/sphinx/using/backends/cloud.rst index 2395dd6d3b1..d2044d64e9e 100644 --- a/docs/sphinx/using/backends/cloud.rst +++ b/docs/sphinx/using/backends/cloud.rst @@ -5,12 +5,7 @@ CUDA-Q provides a number of options to access hardware resources (GPUs and QPUs) .. toctree:: :maxdepth: 1 - + Amazon Braket (braket) -<<<<<<< HEAD Scaleway QaaS (scaleway) -======= - NVIDIA Quantum Cloud (nvqc) Qbraid - ->>>>>>> 17f25cf4 (qBraid integration MVP (#4)) diff --git a/docs/sphinx/using/backends/cloud/qbraid.rst b/docs/sphinx/using/backends/cloud/qbraid.rst index 91184e6b934..a7e7fe4a2ae 100644 --- a/docs/sphinx/using/backends/cloud/qbraid.rst +++ b/docs/sphinx/using/backends/cloud/qbraid.rst @@ -59,4 +59,3 @@ Submission from C++ nvq++ --emulate --target qbraid src.cpp To see a complete example for using IonQ's backends, take a look at our :doc:`C++ examples <../../examples/examples>`. - \ No newline at end of file diff --git a/docs/sphinx/using/backends/hardware/iontrap.rst b/docs/sphinx/using/backends/hardware/iontrap.rst index 3d5db2a90e4..0dc69de2177 100644 --- a/docs/sphinx/using/backends/hardware/iontrap.rst +++ b/docs/sphinx/using/backends/hardware/iontrap.rst @@ -31,7 +31,7 @@ Submitting By default, quantum kernel code will be submitted to the IonQ simulator. - .. note:: + .. note:: A "target" in :code:`cudaq` refers to a quantum compute provider, such as :code:`ionq`. However, IonQ's documentation uses the term "target" to refer to specific QPU's themselves. @@ -70,7 +70,7 @@ Submitting This will take the API key and handle all authentication with, and submission to, the IonQ QPU(s). By default, quantum kernel code will be submitted to the IonQsimulator. - .. note:: + .. note:: A "target" in :code:`cudaq` refers to a quantum compute provider, such as :code:`ionq`. However, IonQ's documentation uses the term "target" to refer to specific QPU's themselves. @@ -105,7 +105,7 @@ Setting Credentials ``````````````````` Programmers of CUDA-Q may access the Quantinuum API from either -C++ or Python. Quantinuum requires a credential configuration file. +C++ or Python. Quantinuum requires a credential configuration file. The configuration file can be generated as follows, replacing the ``email`` and ``credentials`` in the first line with your Quantinuum account details. @@ -134,8 +134,8 @@ Create a project in the Nexus portal. You can find the project ID in the URL of .. tab:: Python - - The backend to which quantum kernels are submitted + + The backend to which quantum kernels are submitted can be controlled with the ``cudaq.set_target()`` function. .. code:: python @@ -155,15 +155,15 @@ Create a project in the Nexus portal. You can find the project ID in the URL of cudaq.set_target('quantinuum', machine='H2-2') where ``H2-2`` is an example of a physical QPU. Hardware specific - emulators may be accessed by appending an ``E`` to the end (e.g, ``H2-2E``). For - access to the syntax checker for the provided machine, you may append an ``SC`` + emulators may be accessed by appending an ``E`` to the end (e.g, ``H2-2E``). For + access to the syntax checker for the provided machine, you may append an ``SC`` to the end (e.g, ``H2-1SC``). - For a comprehensive list of available machines, login to your `Quantinuum Nexus user account `__ + For a comprehensive list of available machines, login to your `Quantinuum Nexus user account `__ and navigate to the "Profile" tab, where you should find a table titled "Quantinuum Systems Access". To emulate the Quantinuum machine locally, without submitting through the cloud, - you can set the ``emulate`` flag to ``True``. This will emit any target + you can set the ``emulate`` flag to ``True``. This will emit any target specific compiler warnings and diagnostics, before running a noise free emulation. You do not need to specify project or machine when emulating. @@ -175,7 +175,7 @@ Create a project in the Nexus portal. You can find the project ID in the URL of the ``shots_count`` argument to ``cudaq.sample`` or ``cudaq.observe``. By default, the ``shots_count`` is set to 1000. - .. code:: python + .. code:: python cudaq.sample(kernel, shots_count=10000) @@ -183,7 +183,7 @@ Create a project in the Nexus portal. You can find the project ID in the URL of .. tab:: C++ To target quantum kernel code for execution in the Quantinuum backends, - pass the flag ``--target quantinuum`` to the ``nvq++`` compiler. CUDA-Q will + pass the flag ``--target quantinuum`` to the ``nvq++`` compiler. CUDA-Q will authenticate via the Quantinuum REST API using the credential in your configuration file. By default, quantum kernel code will be submitted to the Quantinuum syntax checker. Submission to the syntax checker merely validates the program; the kernels are not executed. @@ -202,15 +202,15 @@ Create a project in the Nexus portal. You can find the project ID in the URL of nvq++ --target quantinuum --quantinuum-machine H2-2 src.cpp ... where ``H2-2`` is an example of a physical QPU. Hardware specific - emulators may be accessed by appending an ``E`` to the end (e.g, ``H2-2E``). For - access to the syntax checker for the provided machine, you may append an ``SC`` + emulators may be accessed by appending an ``E`` to the end (e.g, ``H2-2E``). For + access to the syntax checker for the provided machine, you may append an ``SC`` to the end (e.g, ``H2-1SC``). - For a comprehensive list of available machines, login to your `Quantinuum Nexus user account `__ + For a comprehensive list of available machines, login to your `Quantinuum Nexus user account `__ and navigate to the "Profile" tab, where you should find a table titled "Quantinuum Systems Access". To emulate the Quantinuum machine locally, without submitting through the cloud, - you can pass the ``--emulate`` flag to ``nvq++``. This will emit any target + you can pass the ``--emulate`` flag to ``nvq++``. This will emit any target specific compiler warnings and diagnostics, before running a noise free emulation. You do not need to specify project or machine when emulating. @@ -218,16 +218,15 @@ Create a project in the Nexus portal. You can find the project ID in the URL of nvq++ --emulate --target quantinuum src.cpp -.. note:: +.. note:: -<<<<<<< HEAD Quantinuum's syntax checker for Helios (e.g., ``Helios-1SC``) only performs QIR code validation and does not return any results. Thus, it always returns an empty result set. This is different from other Quantinuum backends (e.g., ``H2-1SC``) where the syntax checker returns dummy results. As a result, when using the Helios syntax checker, we may receive this warning message: .. code:: text - - WARNING: this kernel invocation produced 0 shots worth of results when executed. + + WARNING: this kernel invocation produced 0 shots worth of results when executed. It means that the kernel was successfully validated, but no execution results are available. To get results, please submit to the Helios emulator (e.g., ``Helios-1E``) or the actual quantum device (e.g., ``Helios-1``). @@ -236,16 +235,15 @@ Create a project in the Nexus portal. You can find the project ID in the URL of To see a complete example, take a look at :ref:`Quantinuum examples `. -.. note:: +.. note:: In local emulation mode (``emulate`` flag set to ``True``), the program will be executed on the :ref:`default simulator `. - The environment variable ``CUDAQ_DEFAULT_SIMULATOR`` can be used to change the emulation simulator. - + The environment variable ``CUDAQ_DEFAULT_SIMULATOR`` can be used to change the emulation simulator. + For example, the simulation floating point accuracy and/or the simulation capabilities (e.g., maximum number of qubits, supported quantum gates), - depend on the selected simulator. - + depend on the selected simulator. + Any environment variables must be set prior to setting the target or running "`import cudaq`". -======= QBRAID +++++++ @@ -310,5 +308,3 @@ Submitting nvq++ --emulate --target qbraid src.cpp To see a complete example for using IonQ's backends, take a look at our :doc:`C++ examples <../../examples/examples>`. - ->>>>>>> 17f25cf4 (qBraid integration MVP (#4)) From d74243dd59e94091acae92bedd96ae41332bde68 Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Fri, 13 Mar 2026 13:47:31 +0530 Subject: [PATCH 04/18] add: api_key and device to set_target for qbraid --- .../rest/helpers/qbraid/QbraidServerHelper.cpp | 17 +++++++++++++++-- .../default/rest/helpers/qbraid/qbraid.yml | 9 +++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index 5e930c0f2da..3cd688ec579 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -20,11 +20,24 @@ class QbraidServerHelper : public ServerHelper { backendConfig.clear(); backendConfig["url"] = getValueOrDefault(config, "url", DEFAULT_URL); - backendConfig["device_id"] = getValueOrDefault(config, "device_id", DEFAULT_DEVICE); backendConfig["user_agent"] = "cudaq/" + std::string(cudaq::getVersion()); backendConfig["qubits"] = std::to_string(DEFAULT_QUBITS); - backendConfig["api_key"] = getEnvVar("QBRAID_API_KEY", "", true); + // Accept "machine" as a user-friendly alias for device_id + // Usage: cudaq.set_target("qbraid", machine="ionq:ionq:sim:simulator") + if (!config["machine"].empty()) { + backendConfig["device_id"] = config["machine"]; + } else { + backendConfig["device_id"] = getValueOrDefault(config, "device_id", DEFAULT_DEVICE); + } + + // Accept api_key from target arguments, fall back to QBRAID_API_KEY env var + // Usage: cudaq.set_target("qbraid", api_key="my-key") + if (!config["api_key"].empty()) { + backendConfig["api_key"] = config["api_key"]; + } else { + backendConfig["api_key"] = getEnvVar("QBRAID_API_KEY", "", true); + } backendConfig["job_path"] = backendConfig["url"] + "/jobs"; backendConfig["results_output_dir"] = getValueOrDefault(config, "results_output_dir", "./qbraid_results"); diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml index 5132a74d1a7..99a0f17ee7d 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml @@ -26,5 +26,10 @@ target-arguments: - key: machine required: false type: string - platform-arg: qpu - help-string: "Specify the qBraid QPU." \ No newline at end of file + platform-arg: qpu + help-string: "Specify the qBraid QPU." + - key: api_key + required: false + type: string + platform-arg: api_key + help-string: "Specify the qBraid API key." From 9cd62cffc6562aef4c425857cb97edf80c5a3407 Mon Sep 17 00:00:00 2001 From: Harshit Date: Wed, 15 Apr 2026 10:19:50 +0000 Subject: [PATCH 05/18] fix: submodule hashes and v2 platform implementation and test --- lib/Optimizer/CodeGen/Passes.cpp | 5 +- python/tests/backends/test_Qbraid.py | 184 ++++++++++++++++++ .../rest/helpers/qbraid/CMakeLists.txt | 6 +- .../helpers/qbraid/QbraidServerHelper.cpp | 139 +++++++------ .../default/rest/helpers/qbraid/qbraid.yml | 4 +- tpls/Stim | 2 +- tpls/cpr | 2 +- tpls/fmt | 2 +- tpls/spdlog | 2 +- unittests/backends/qbraid/CMakeLists.txt | 21 +- .../qbraid/QbraidStartServerAndTest.sh.in | 4 + unittests/backends/qbraid/QbraidTester.cpp | 120 ++++++++---- utils/mock_qpu/qbraid/__init__.py | 37 +++- 13 files changed, 397 insertions(+), 131 deletions(-) create mode 100644 python/tests/backends/test_Qbraid.py diff --git a/lib/Optimizer/CodeGen/Passes.cpp b/lib/Optimizer/CodeGen/Passes.cpp index ce9795bf0c8..dc41359f93b 100644 --- a/lib/Optimizer/CodeGen/Passes.cpp +++ b/lib/Optimizer/CodeGen/Passes.cpp @@ -105,10 +105,9 @@ static void addQbraidPipeline(OpPassManager &pm) { std::string basis[] = { "h", "s", "t", "rx", "ry", "rz", "x", "y", "z", "x(1)", }; - BasisConversionPassOptions options; + BasisConversionOptions options; options.basis = basis; - options.disabledPatterns = z_disabledPatterns; - pm.addPass(createBasisConversionPass(options)); + pm.addPass(createBasisConversion(options)); } void cudaq::opt::registerTargetPipelines() { diff --git a/python/tests/backends/test_Qbraid.py b/python/tests/backends/test_Qbraid.py new file mode 100644 index 00000000000..6b6d1599753 --- /dev/null +++ b/python/tests/backends/test_Qbraid.py @@ -0,0 +1,184 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +import os +from multiprocessing import Process + +import cudaq +import pytest +from cudaq import spin +from network_utils import check_server_connection + +try: + from utils.mock_qpu.qbraid import startServer +except ImportError: + print("Mock qpu not available, skipping qBraid tests.") + pytest.skip("Mock qpu not available.", allow_module_level=True) + +port = 62452 + +# Default machine for tests. Mirrors the real qBraid device string format. +TEST_MACHINE = "ionq:ionq:sim:simulator" +TEST_API_KEY = "00000000000000000000000000000000" + + +# The qbraid mock server in utils/mock_qpu/qbraid/__init__.py doesn't simulate +# quantum mechanics - it only inspects the QASM for `h` and `measure` ops and +# generates random outcomes for qubits with H. It does NOT model entanglement +# via CNOT. Assertions below reflect the mock's behavior, not physical truth. + + +def _set_qbraid_target(**overrides): + """Call set_target with the canonical qbraid args plus any overrides. + + Uses the documented target arguments (`machine`, `api_key`) plus `url` + which is accepted by the helper for test/mock overrides. + """ + kwargs = { + "url": f"http://localhost:{port}", + "machine": TEST_MACHINE, + "api_key": TEST_API_KEY, + } + kwargs.update(overrides) + cudaq.set_target("qbraid", **kwargs) + + +@pytest.fixture(scope="session", autouse=True) +def startUpMockServer(): + cudaq.set_random_seed(13) + os.environ["QBRAID_API_KEY"] = TEST_API_KEY + + _set_qbraid_target() + + p = Process(target=startServer, args=(port,)) + p.start() + + if not check_server_connection(port): + p.terminate() + pytest.exit("Mock server did not start in time, skipping tests.", + returncode=1) + + yield "Server started." + + p.terminate() + + +@pytest.fixture(scope="function", autouse=True) +def configureTarget(): + _set_qbraid_target() + yield "Running the test." + cudaq.reset_target() + + +def _make_h_kernel(): + """H on q[0], CX to q[1], measure both. Mock only sees H on q[0].""" + kernel = cudaq.make_kernel() + qubits = kernel.qalloc(2) + kernel.h(qubits[0]) + kernel.cx(qubits[0], qubits[1]) + kernel.mz(qubits) + return kernel + + +def test_qbraid_sample(): + counts = cudaq.sample(_make_h_kernel()) + # Mock: q[0] superposition -> {"0","1"}, q[1] fixed -> "0" + # Observed outcomes: "00" and "10" + assert len(counts) == 2 + assert "00" in counts + assert "10" in counts + + +def test_qbraid_sample_async(): + future = cudaq.sample_async(_make_h_kernel()) + counts = future.get() + assert len(counts) == 2 + assert "00" in counts + assert "10" in counts + + +def test_qbraid_sample_async_persist_future(): + future = cudaq.sample_async(_make_h_kernel()) + futureAsString = str(future) + + readIn = cudaq.AsyncSampleResult(futureAsString) + counts = readIn.get() + assert len(counts) == 2 + assert "00" in counts + assert "10" in counts + + +def _make_vqe_ansatz(): + kernel, theta = cudaq.make_kernel(float) + qreg = kernel.qalloc(2) + kernel.x(qreg[0]) + kernel.ry(theta, qreg[1]) + kernel.cx(qreg[1], qreg[0]) + hamiltonian = (5.907 - 2.1433 * spin.x(0) * spin.x(1) - + 2.1433 * spin.y(0) * spin.y(1) + 0.21829 * spin.z(0) - + 6.125 * spin.z(1)) + return kernel, hamiltonian + + +def test_qbraid_observe(): + kernel, hamiltonian = _make_vqe_ansatz() + res = cudaq.observe(kernel, hamiltonian, 0.59) + # Mock outcomes are random; just verify the roundtrip returned a finite value. + val = res.expectation() + assert isinstance(val, float) + assert val == val # NaN check + + +def test_qbraid_observe_async_persist_future(): + kernel, hamiltonian = _make_vqe_ansatz() + + future = cudaq.observe_async(kernel, hamiltonian, 0.59) + futureAsString = str(future) + + readIn = cudaq.AsyncObserveResult(futureAsString, hamiltonian) + res = readIn.get() + val = res.expectation() + assert isinstance(val, float) + assert val == val + + +def test_qbraid_api_key_via_target_arg_without_env_var(): + """When QBRAID_API_KEY env var is absent, api_key kwarg must work.""" + saved = os.environ.pop("QBRAID_API_KEY", None) + try: + _set_qbraid_target(api_key=TEST_API_KEY) + + kernel = cudaq.make_kernel() + qubit = kernel.qalloc() + kernel.h(qubit) + kernel.mz(qubit) + + counts = cudaq.sample(kernel) + assert len(counts) >= 1 + finally: + if saved is not None: + os.environ["QBRAID_API_KEY"] = saved + + +def test_qbraid_machine_alternative_device(): + """A different machine string is accepted via the target arg.""" + _set_qbraid_target(machine="aws:aws:sim:sv1") + + kernel = cudaq.make_kernel() + qubit = kernel.qalloc() + kernel.h(qubit) + kernel.mz(qubit) + + counts = cudaq.sample(kernel) + assert len(counts) >= 1 + + +# leave for gdb debugging +if __name__ == "__main__": + loc = os.path.abspath(__file__) + pytest.main([loc, "-s"]) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt index 05b059ecd25..823c01fd100 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt @@ -10,8 +10,8 @@ add_target_config(qbraid) add_library(cudaq-serverhelper-qbraid SHARED QbraidServerHelper.cpp ) target_link_libraries(cudaq-serverhelper-qbraid - PUBLIC - cudaq-common - fmt::fmt-header-only + PUBLIC + cudaq-common + cudaq-logger ) install(TARGETS cudaq-serverhelper-qbraid DESTINATION lib) \ No newline at end of file diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index 3cd688ec579..8b26f8b3d45 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -1,8 +1,10 @@ -#include "common/Logger.h" #include "common/RestClient.h" #include "common/ServerHelper.h" #include "cudaq/Support/Version.h" +#include "cudaq/runtime/logger/logger.h" #include "cudaq/utils/cudaq_utils.h" +#include +#include #include namespace cudaq { @@ -40,9 +42,6 @@ class QbraidServerHelper : public ServerHelper { } backendConfig["job_path"] = backendConfig["url"] + "/jobs"; - backendConfig["results_output_dir"] = getValueOrDefault(config, "results_output_dir", "./qbraid_results"); - backendConfig["results_file_prefix"] = getValueOrDefault(config, "results_file_prefix", "qbraid_job_"); - if (!config["shots"].empty()) { backendConfig["shots"] = config["shots"]; this->setShots(std::stoul(config["shots"])); @@ -57,10 +56,6 @@ class QbraidServerHelper : public ServerHelper { for (const auto &[key, value] : backendConfig) { cudaq::info(" {} = {}", key, value); } - - std::string resultsDir = backendConfig["results_output_dir"]; - std::filesystem::create_directories(resultsDir); - cudaq::info("Created results directory: {}", resultsDir); } ServerJobPayload @@ -73,12 +68,13 @@ class QbraidServerHelper : public ServerHelper { for (auto &circuitCode : circuitCodes) { ServerMessage job; job["deviceQrn"] = backendConfig.at("device_id"); - job["shots"] = std::stoi(backendConfig.at("shots")); + // Use the per-call shots (set via cudaq::sample(..., shots_count=N)) + job["shots"] = shots; // v2 API: program is a structured object with format and data nlohmann::json program; program["format"] = "qasm2"; - program["data"] = circuitCode.code; + program["data"] = normalizeClassicalRegisters(circuitCode.code); job["program"] = program; // v2 API: name is a top-level field (not nested under tags) @@ -118,11 +114,6 @@ class QbraidServerHelper : public ServerHelper { return backendConfig.at("job_path") + "/" + jobId + "/result"; } - std::string constructGetProgramPath(const std::string &jobId) { - // v2 API: /jobs/{jobQrn}/program - return backendConfig.at("job_path") + "/" + jobId + "/program"; - } - bool jobIsDone(ServerMessage &getJobResponse) override { std::string status; @@ -140,37 +131,25 @@ class QbraidServerHelper : public ServerHelper { } if (status == "FAILED" || status == "COMPLETED" || status == "CANCELLED") { - saveResponseToFile(getJobResponse); return true; } return false; } - // Fetch the original program from v2 endpoint - std::string getJobProgram(const ServerMessage &response, const std::string &jobId) override { - auto programPath = constructGetProgramPath(jobId); - auto headers = getHeaders(); - - cudaq::info("Fetching job program from v2 endpoint: {}", programPath); - RestClient client; - auto programJson = client.get("", programPath, headers, true); - - // v2 API: program content at data.data, format at data.format - if (programJson.contains("data") && programJson["data"].contains("data")) { - cudaq::info("Retrieved program (format: {})", - programJson["data"].value("format", "unknown")); - return programJson["data"]["data"].get(); - } - - throw std::runtime_error("Invalid program response format: " + programJson.dump()); - } - - // Fetch results from v2 results endpoint with retry logic + // Fetch results from v2 results endpoint with retry logic. + // + // Rationale: qbraid's v2 API has a window where status transitions to + // COMPLETED before the result payload is queryable on /result, so /result + // returns {success: false, data: {message: "not yet available"}}. The retry + // with backoff absorbs that race. + // + // Exercised deterministically via the mock's POST /test/delay_next_results + // endpoint (see checkResultRetry / checkResultRetryExhaustion tests). cudaq::sample_result processResults(ServerMessage &getJobResponse, std::string &jobId) override { - int maxRetries = 5; - int waitTime = 2; - float backoffFactor = 2.0; + const int maxRetries = 3; + const int waitTime = 2; + const float backoffFactor = 2.0; for (int attempt = 0; attempt < maxRetries; ++attempt) { try { @@ -242,34 +221,68 @@ class QbraidServerHelper : public ServerHelper { } private: - void saveResponseToFile(const ServerMessage &response, const std::string &identifier = "") { - try { - std::string outputDir = backendConfig.at("results_output_dir"); - std::string filePrefix = backendConfig.at("results_file_prefix"); - - // Create a unique filename using timestamp if no identifier is provided - std::string filename; - if (identifier.empty()) { - auto now = std::chrono::system_clock::now(); - auto timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); - filename = outputDir + "/" + filePrefix + std::to_string(timestamp) + ".json"; - } else { - filename = outputDir + "/" + filePrefix + identifier + ".json"; - } + // Merge multiple single-bit classical registers emitted by nvq++'s QASM 2 + // codegen into a single multi-bit `creg c[N]`. This is required to unblock + // qBraid-routed hardware backends. + // + // Context: nvq++ emits one `creg varK[1];` per measurement. AWS Braket's + // classical simulators (SV1, DM1, TN1) tolerate that via lenient register + // concatenation, but stricter hardware transpilers reject it: + // - IQM (Garnet etc.): returns only the first register -> 1-bit results + // - Rigetti: collapses all registers onto b[0] -> "bit already in use" + // - IonQ-via-Braket: similar strict behavior + // Normalizing to a single register is the canonical QASM 2 form and is + // accepted uniformly by every qBraid-reachable backend. + std::string normalizeClassicalRegisters(const std::string &qasm) const { + static const std::regex cregDeclRx( + R"(creg\s+(\w+)\s*\[\s*(\d+)\s*\]\s*;)"); + + std::vector> cregs; + for (auto it = std::sregex_iterator(qasm.begin(), qasm.end(), cregDeclRx); + it != std::sregex_iterator(); ++it) { + cregs.emplace_back((*it)[1].str(), std::stoi((*it)[2].str())); + } - std::ofstream outputFile(filename); - if (!outputFile.is_open()) { - cudaq::info("Failed to open file for writing: {}", filename); - return; - } + // Nothing to do if the QASM already has a single classical register. + if (cregs.size() <= 1) + return qasm; + + std::map offsetByName; + int totalBits = 0; + for (auto &[name, size] : cregs) { + offsetByName[name] = totalBits; + totalBits += size; + } - outputFile << response.dump(2); - outputFile.close(); + std::string out = qasm; - cudaq::info("Response saved to file: {}", filename); - } catch (const std::exception &e) { - cudaq::info("Error saving response to file: {}", e.what()); + // Rewrite every `-> NAME[i]` target BEFORE we mutate the creg declarations. + for (auto &[name, size] : cregs) { + int base = offsetByName[name]; + for (int i = 0; i < size; ++i) { + std::regex measureTargetRx("->\\s*" + name + "\\s*\\[\\s*" + + std::to_string(i) + "\\s*\\]"); + out = std::regex_replace(out, measureTargetRx, + "-> qbraid__creg__[" + std::to_string(base + i) + "]"); + } + } + + // Replace the first declaration with the merged register. + out = std::regex_replace(out, cregDeclRx, + "creg qbraid__creg__[" + + std::to_string(totalBits) + "];", + std::regex_constants::format_first_only); + + // Remove the remaining original declarations. + for (size_t i = 1; i < cregs.size(); ++i) { + std::regex toRemove("creg\\s+" + cregs[i].first + + "\\s*\\[\\s*\\d+\\s*\\]\\s*;\\s*"); + out = std::regex_replace(out, toRemove, ""); } + + cudaq::info("Normalized {} classical registers into single qbraid__creg__[{}]", + cregs.size(), totalBits); + return out; } RestHeaders getHeaders() override { diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml index 99a0f17ee7d..0ee345afd43 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml @@ -15,8 +15,8 @@ config: gen-target-backend: true # Add the rest-qpu library to the link list link-libs: ["-lcudaq-rest-qpu"] - # Define the lowering pipeline - platform-lowering-config: "classical-optimization-pipeline,globalize-array-values,func.func(state-prep),unitary-synthesis,canonicalize,apply-op-specialization,aggressive-early-inlining,classical-optimization-pipeline,func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),decomposition{enable-patterns=SToR1,TToR1,CCZToCX,CRyToCX,CRxToCX,R1AdjToR1,RxAdjToRx,RyAdjToRy,RzAdjToRz},quake-to-cc-prep,func.func(memtoreg{quantum=0}),symbol-dce" + # Define the JIT lowering pipeline + jit-mid-level-pipeline: "qbraid-gate-set-mapping" # Tell the rest-qpu that we are generating OpenQASM. codegen-emission: qasm2 # Library mode is only for simulators, physical backends must turn this off diff --git a/tpls/Stim b/tpls/Stim index 47190f4a3af..42e0b9e0991 160000 --- a/tpls/Stim +++ b/tpls/Stim @@ -1 +1 @@ -Subproject commit 47190f4a3afb104c9f0068d0be9fea87d2894a70 +Subproject commit 42e0b9e099180e8570407c33f87b4683cac00d81 diff --git a/tpls/cpr b/tpls/cpr index 871ed52d350..d202b82fbcc 160000 --- a/tpls/cpr +++ b/tpls/cpr @@ -1 +1 @@ -Subproject commit 871ed52d350214a034f6ef8a3b8f51c5ce1bd400 +Subproject commit d202b82fbccf897604a18e035c09e1330dffd082 diff --git a/tpls/fmt b/tpls/fmt index ba50c19e827..fc8d07cfe54 160000 --- a/tpls/fmt +++ b/tpls/fmt @@ -1 +1 @@ -Subproject commit ba50c19e827383bd5dacb74189fb4852c8dcbdae +Subproject commit fc8d07cfe54ba9f5019453dfdb112491246ee017 diff --git a/tpls/spdlog b/tpls/spdlog index edc51df1bda..287333ee005 160000 --- a/tpls/spdlog +++ b/tpls/spdlog @@ -1 +1 @@ -Subproject commit edc51df1bdad8667b628999394a1e7c4dc6f3658 +Subproject commit 287333ee00555aaece5a5cf6acc9040563c6f642 diff --git a/unittests/backends/qbraid/CMakeLists.txt b/unittests/backends/qbraid/CMakeLists.txt index 05ca3c19550..390d20cc896 100644 --- a/unittests/backends/qbraid/CMakeLists.txt +++ b/unittests/backends/qbraid/CMakeLists.txt @@ -6,22 +6,11 @@ # the terms of the Apache License 2.0 which accompanies this distribution. # # ============================================================================ # -add_executable(test_qbraid QbraidTester.cpp) -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) - target_link_options(test_qbraid PRIVATE -Wl,--no-as-needed) -endif() -target_compile_definitions(test_qbraid PRIVATE -DNVQIR_BACKEND_NAME=qbraid) -target_include_directories(test_qbraid PRIVATE ../..) -target_link_libraries(test_qbraid - PRIVATE fmt::fmt-header-only - cudaq-common - cudaq - cudaq-builder - cudaq-mlir-runtime - cudaq-rest-qpu - cudaq-platform-default - gtest_main) - +add_backend_unittest_executable(test_qbraid + SOURCES QbraidTester.cpp + BACKEND qbraid + BACKEND_CONFIG "qbraid emulate=false url=http://localhost:62452 api_key=00000000000000000000000000000000" +) configure_file("QbraidStartServerAndTest.sh.in" "${CMAKE_BINARY_DIR}/unittests/backends/qbraid/QbraidStartServerAndTest.sh" @ONLY) add_test(NAME qbraid-tests COMMAND bash QbraidStartServerAndTest.sh WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/unittests/backends/qbraid/) diff --git a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in index 72ec44e9433..bd5c15b9af7 100644 --- a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in +++ b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in @@ -33,6 +33,10 @@ while ! checkServerConnection; do exit 99 fi done +# api_key is passed via the backend config (see CMakeLists BACKEND_CONFIG), +# so we unset QBRAID_API_KEY to force the helper to use the config value. +# checkApiKeyFromTarget asserts the env var is null. +unset QBRAID_API_KEY # Run the tests ./test_qbraid # Did they fail? diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp index e3e94fb8087..0d5b1fb1b09 100644 --- a/unittests/backends/qbraid/QbraidTester.cpp +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -8,6 +8,7 @@ #include "CUDAQTestUtils.h" #include "common/FmtCore.h" +#include "common/RestClient.h" #include "cudaq/algorithm.h" #include #include @@ -19,17 +20,15 @@ std::string backendStringTemplate = "qbraid;emulate;false;url;http://localhost:{}"; bool isValidExpVal(double value) { - // give us some wiggle room while keep the tests fast - return value < -1.1 && value > -2.3; + // The qbraid mock server doesn't simulate quantum mechanics - X0X1 counts + // are uniform random per 1000-shot sample (std dev ~0.03), so the + // expectation value for this VQE Hamiltonian fluctuates around -2.14 by + // a few hundredths per run. The band below is wide enough (~10 sigma) to + // be stable across test runs while still catching corrupt / NaN results. + return value < -1.0 && value > -3.0; } CUDAQ_TEST(QbraidTester, checkSampleSync) { - auto backendString = - fmt::format(fmt::runtime(backendStringTemplate), mockPort); - - auto &platform = cudaq::get_platform(); - platform.setTargetBackend(backendString); - auto kernel = cudaq::make_kernel(); auto qubit = kernel.qalloc(2); kernel.h(qubit[0]); @@ -41,12 +40,6 @@ CUDAQ_TEST(QbraidTester, checkSampleSync) { } CUDAQ_TEST(QbraidTester, checkSampleAsync) { - auto backendString = - fmt::format(fmt::runtime(backendStringTemplate), mockPort); - - auto &platform = cudaq::get_platform(); - platform.setTargetBackend(backendString); - auto kernel = cudaq::make_kernel(); auto qubit = kernel.qalloc(2); kernel.h(qubit[0]); @@ -58,12 +51,6 @@ CUDAQ_TEST(QbraidTester, checkSampleAsync) { } CUDAQ_TEST(QbraidTester, checkSampleAsyncLoadFromFile) { - auto backendString = - fmt::format(fmt::runtime(backendStringTemplate), mockPort); - - auto &platform = cudaq::get_platform(); - platform.setTargetBackend(backendString); - auto kernel = cudaq::make_kernel(); auto qubit = kernel.qalloc(2); kernel.h(qubit[0]); @@ -86,12 +73,6 @@ CUDAQ_TEST(QbraidTester, checkSampleAsyncLoadFromFile) { } CUDAQ_TEST(QbraidTester, checkObserveSync) { - auto backendString = - fmt::format(fmt::runtime(backendStringTemplate), mockPort); - - auto &platform = cudaq::get_platform(); - platform.setTargetBackend(backendString); - auto [kernel, theta] = cudaq::make_kernel(); auto qubit = kernel.qalloc(2); kernel.x(qubit[0]); @@ -109,12 +90,6 @@ CUDAQ_TEST(QbraidTester, checkObserveSync) { } CUDAQ_TEST(QbraidTester, checkObserveAsync) { - auto backendString = - fmt::format(fmt::runtime(backendStringTemplate), mockPort); - - auto &platform = cudaq::get_platform(); - platform.setTargetBackend(backendString); - auto [kernel, theta] = cudaq::make_kernel(); auto qubit = kernel.qalloc(2); kernel.x(qubit[0]); @@ -134,12 +109,6 @@ CUDAQ_TEST(QbraidTester, checkObserveAsync) { } CUDAQ_TEST(QbraidTester, checkObserveAsyncLoadFromFile) { - auto backendString = - fmt::format(fmt::runtime(backendStringTemplate), mockPort); - - auto &platform = cudaq::get_platform(); - platform.setTargetBackend(backendString); - auto [kernel, theta] = cudaq::make_kernel(); auto qubit = kernel.qalloc(2); kernel.x(qubit[0]); @@ -169,8 +138,81 @@ CUDAQ_TEST(QbraidTester, checkObserveAsyncLoadFromFile) { EXPECT_TRUE(isValidExpVal(result.expectation())); } +// Every test in this file runs through the backend configured by +// add_backend_unittest_executable in CMakeLists, which passes api_key via the +// target config (BACKEND_CONFIG). QBRAID_API_KEY env var is NOT set by the +// launch script, so a successful sample here exercises the target-arg path. +CUDAQ_TEST(QbraidTester, checkApiKeyFromTarget) { + ASSERT_EQ(std::getenv("QBRAID_API_KEY"), nullptr) + << "QBRAID_API_KEY should not be set; this test verifies the " + "api_key=... target-arg path."; + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + auto counts = cudaq::sample(kernel); + EXPECT_GE(counts.size(), 1u); +} + +CUDAQ_TEST(QbraidTester, checkJobFailure) { + // Arm the mock to fail the next submitted job. + cudaq::RestClient client; + nlohmann::json body = nlohmann::json::object(); + std::map headers; + auto armed = client.post("http://localhost:62452/", "test/fail_next", body, + headers, /*enableLogging=*/false); + ASSERT_TRUE(armed.value("armed", false)); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + EXPECT_ANY_THROW({ (void)cudaq::sample(kernel); }); +} + +// Arm the mock to make the next N /result calls return "not yet available", +// so processResults must retry. maxRetries is 3, so 2 delays should succeed. +CUDAQ_TEST(QbraidTester, checkResultRetry) { + cudaq::RestClient client; + nlohmann::json body = nlohmann::json::object(); + std::map headers; + auto armed = + client.post("http://localhost:62452/", "test/delay_next_results/2", body, + headers, /*enableLogging=*/false); + ASSERT_EQ(armed.value("remaining", -1), 2); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + auto counts = cudaq::sample(kernel); + EXPECT_GE(counts.size(), 1u); +} + +// Arm enough delays to exhaust the retry budget (maxRetries = 3). Sample must +// throw. Uses 10 so the retry loop can never succeed. +CUDAQ_TEST(QbraidTester, checkResultRetryExhaustion) { + cudaq::RestClient client; + nlohmann::json body = nlohmann::json::object(); + std::map headers; + auto armed = + client.post("http://localhost:62452/", "test/delay_next_results/10", body, + headers, /*enableLogging=*/false); + ASSERT_EQ(armed.value("remaining", -1), 10); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + EXPECT_ANY_THROW({ (void)cudaq::sample(kernel); }); +} + int main(int argc, char **argv) { - setenv("QBRAID_API_KEY", "00000000000000000000000000000000", 0); ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); return ret; diff --git a/utils/mock_qpu/qbraid/__init__.py b/utils/mock_qpu/qbraid/__init__.py index 1bb225a59f7..70686afd7c3 100644 --- a/utils/mock_qpu/qbraid/__init__.py +++ b/utils/mock_qpu/qbraid/__init__.py @@ -38,6 +38,13 @@ class Job(BaseModel): JOBS_MOCK_DB = {} JOBS_MOCK_RESULTS = {} +# Testing toggle: when True, the next job submitted via POST /jobs is created +# with status FAILED. Consumed (reset to False) after use. +FAIL_NEXT_JOB = {"enabled": False} +# Testing counter: how many upcoming GET /jobs/{id}/result calls should return +# success=false (simulating the qbraid v2 race where status=COMPLETED before +# results are queryable). Decrements on each /result call until 0. +DELAY_RESULTS_COUNT = {"remaining": 0} def count_qubits(qasm: str) -> int: @@ -163,6 +170,17 @@ async def postJob(job: Job, x_api_key: Optional[str] = Header(None, alias="X-API newId = str(uuid.uuid4()) + # Test hook: fail this job immediately if the toggle was armed. + if FAIL_NEXT_JOB["enabled"]: + FAIL_NEXT_JOB["enabled"] = False + job_data = { + "status": "FAILED", + "statusText": "Triggered failure for testing", + **job.model_dump(), + } + JOBS_MOCK_DB[newId] = job_data + return {"success": True, "data": {"jobQrn": newId, "status": "FAILED"}} + # Extract QASM from the structured program payload counts = simulate_job(job.program.data, job.shots) @@ -175,6 +193,20 @@ async def postJob(job: Job, x_api_key: Optional[str] = Header(None, alias="X-API return {"success": True, "data": {"jobQrn": newId, "status": "INITIALIZING"}} +# Test-only: arm a failure for the next submitted job. +@app.post("/test/fail_next") +async def armFailNext(): + FAIL_NEXT_JOB["enabled"] = True + return {"armed": True} + + +# Test-only: force the next N /result calls to return success=false. +@app.post("/test/delay_next_results/{count}") +async def armDelayResults(count: int = Path(...)): + DELAY_RESULTS_COUNT["remaining"] = count + return {"remaining": count} + + # v2 API: GET /jobs/{job_qrn} @app.get("/jobs/{job_id}") async def getJob( @@ -244,7 +276,10 @@ async def getJobResult( if job_id not in JOBS_MOCK_RESULTS: raise HTTPException(status_code=500, detail="Job results not found") - if random.random() < 0.2: + # Test hook: return "not yet available" for the next N /result calls if + # the delay counter is armed. Decrements on each call. + if DELAY_RESULTS_COUNT["remaining"] > 0: + DELAY_RESULTS_COUNT["remaining"] -= 1 return { "success": False, "data": { From 3b0a1e4c84bab1378b4b37b279060143794cb21f Mon Sep 17 00:00:00 2001 From: Harshit Date: Wed, 15 Apr 2026 10:41:29 +0000 Subject: [PATCH 06/18] fix: formatting and headers --- .github/pre-commit/spelling_allowlist.txt | 10 +++ docs/sphinx/targets/cpp/qbraid.cpp | 1 - docs/sphinx/targets/python/qbraid.py | 3 +- lib/Optimizer/CodeGen/Passes.cpp | 4 +- .../helpers/qbraid/QbraidServerHelper.cpp | 84 +++++++++++++------ unittests/backends/qbraid/QbraidTester.cpp | 2 +- utils/mock_qpu/qbraid/__init__.py | 59 +++++++------ 7 files changed, 104 insertions(+), 59 deletions(-) diff --git a/.github/pre-commit/spelling_allowlist.txt b/.github/pre-commit/spelling_allowlist.txt index 64c9c045bed..984c5e929c8 100644 --- a/.github/pre-commit/spelling_allowlist.txt +++ b/.github/pre-commit/spelling_allowlist.txt @@ -1,6 +1,7 @@ ABI AFQMC API +api APIs AST Aer @@ -108,6 +109,8 @@ Photonics PyPI Pygments QAOA +QASM +QBRAID QCI QCaaS QEC @@ -122,6 +125,7 @@ QRMI QTX QX QaaS +Qbraid Qiskit QuEra QuTiP @@ -300,6 +304,7 @@ lossy lvalue macOS makefiles +measurementCounts merchantability mps multinomial @@ -311,6 +316,7 @@ natively normalization nullary nvcc +nvq observables optimizer optimizers @@ -333,20 +339,24 @@ probability programmatically pybind qaoa +qbraid qed qio +qrn quantize quantized qubit qubits qudit qudits +queryable qumode qumodes reStructuredText realtime reconfigurable reproducibility +resultData reusability runtime runtimes diff --git a/docs/sphinx/targets/cpp/qbraid.cpp b/docs/sphinx/targets/cpp/qbraid.cpp index 4b696005582..b6d859ec2de 100644 --- a/docs/sphinx/targets/cpp/qbraid.cpp +++ b/docs/sphinx/targets/cpp/qbraid.cpp @@ -4,7 +4,6 @@ // ``` // This will submit the job to the Qbraid ideal simulator target (default). - #include #include diff --git a/docs/sphinx/targets/python/qbraid.py b/docs/sphinx/targets/python/qbraid.py index 8450e3a6fd8..cf3fe483c6b 100644 --- a/docs/sphinx/targets/python/qbraid.py +++ b/docs/sphinx/targets/python/qbraid.py @@ -15,7 +15,6 @@ def kernel(): x.ctrl(qvector[0], qvector[1]) - # Execute on Qbraid and print out the results. # Option A: @@ -49,4 +48,4 @@ def kernel(): # any remaining classical code in the file will occur only # after the job has been returned from Qbraid. counts = cudaq.sample(kernel) -print(counts) \ No newline at end of file +print(counts) diff --git a/lib/Optimizer/CodeGen/Passes.cpp b/lib/Optimizer/CodeGen/Passes.cpp index dc41359f93b..808db9e3e2a 100644 --- a/lib/Optimizer/CodeGen/Passes.cpp +++ b/lib/Optimizer/CodeGen/Passes.cpp @@ -136,8 +136,8 @@ void cudaq::opt::registerTargetPipelines() { "Convert kernels to Fermioniq gate set.", addFermioniqPipeline); PassPipelineRegistration<>("qbraid-gate-set-mapping", - "Convert kernels to qBraid gate set.", - addQbraidPipeline); + "Convert kernels to qBraid gate set.", + addQbraidPipeline); } void cudaq::opt::registerCodeGenDialect(DialectRegistry ®istry) { diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index 8b26f8b3d45..b53979fd98e 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -1,3 +1,11 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + #include "common/RestClient.h" #include "common/ServerHelper.h" #include "cudaq/Support/Version.h" @@ -30,7 +38,8 @@ class QbraidServerHelper : public ServerHelper { if (!config["machine"].empty()) { backendConfig["device_id"] = config["machine"]; } else { - backendConfig["device_id"] = getValueOrDefault(config, "device_id", DEFAULT_DEVICE); + backendConfig["device_id"] = + getValueOrDefault(config, "device_id", DEFAULT_DEVICE); } // Accept api_key from target arguments, fall back to QBRAID_API_KEY env var @@ -61,7 +70,8 @@ class QbraidServerHelper : public ServerHelper { ServerJobPayload createJob(std::vector &circuitCodes) override { if (backendConfig.find("job_path") == backendConfig.end()) { - throw std::runtime_error("job_path not found in config. Was initialize() called?"); + throw std::runtime_error( + "job_path not found in config. Was initialize() called?"); } std::vector jobs; @@ -90,18 +100,23 @@ class QbraidServerHelper : public ServerHelper { std::string extractJobId(ServerMessage &postResponse) override { // v2 API: jobQrn is nested under data envelope - if (postResponse.contains("data") && postResponse["data"].contains("jobQrn")) { + if (postResponse.contains("data") && + postResponse["data"].contains("jobQrn")) { return postResponse["data"]["jobQrn"].get(); } - throw std::runtime_error("ServerMessage doesn't contain 'data.jobQrn' key."); + throw std::runtime_error( + "ServerMessage doesn't contain 'data.jobQrn' key."); } std::string constructGetJobPath(ServerMessage &postResponse) override { // v2 API: use path parameter instead of query parameter - if (postResponse.contains("data") && postResponse["data"].contains("jobQrn")) { - return backendConfig.at("job_path") + "/" + postResponse["data"]["jobQrn"].get(); + if (postResponse.contains("data") && + postResponse["data"].contains("jobQrn")) { + return backendConfig.at("job_path") + "/" + + postResponse["data"]["jobQrn"].get(); } - throw std::runtime_error("ServerMessage doesn't contain 'data.jobQrn' key."); + throw std::runtime_error( + "ServerMessage doesn't contain 'data.jobQrn' key."); } std::string constructGetJobPath(std::string &jobId) override { @@ -118,7 +133,8 @@ class QbraidServerHelper : public ServerHelper { std::string status; // v2 API: status is nested under data envelope - if (getJobResponse.contains("data") && getJobResponse["data"].contains("status")) { + if (getJobResponse.contains("data") && + getJobResponse["data"].contains("status")) { status = getJobResponse["data"]["status"].get(); cudaq::info("Job status from v2 data envelope: {}", status); } else if (getJobResponse.contains("status")) { @@ -146,7 +162,8 @@ class QbraidServerHelper : public ServerHelper { // // Exercised deterministically via the mock's POST /test/delay_next_results // endpoint (see checkResultRetry / checkResultRetryExhaustion tests). - cudaq::sample_result processResults(ServerMessage &getJobResponse, std::string &jobId) override { + cudaq::sample_result processResults(ServerMessage &getJobResponse, + std::string &jobId) override { const int maxRetries = 3; const int waitTime = 2; const float backoffFactor = 2.0; @@ -156,15 +173,18 @@ class QbraidServerHelper : public ServerHelper { auto resultsPath = constructGetResultsPath(jobId); auto headers = getHeaders(); - cudaq::info("Fetching results from v2 endpoint (attempt {}/{}): {}", attempt + 1, maxRetries, resultsPath); + cudaq::info("Fetching results from v2 endpoint (attempt {}/{}): {}", + attempt + 1, maxRetries, resultsPath); RestClient client; auto resultJson = client.get("", resultsPath, headers, true); // v2 API: error indicated by success=false - if (resultJson.contains("success") && resultJson["success"].is_boolean() - && !resultJson["success"].get()) { + if (resultJson.contains("success") && + resultJson["success"].is_boolean() && + !resultJson["success"].get()) { std::string errorMsg = "Results not yet available"; - if (resultJson.contains("data") && resultJson["data"].contains("message")) { + if (resultJson.contains("data") && + resultJson["data"].contains("message")) { errorMsg = resultJson["data"]["message"].get(); } cudaq::info("Results endpoint returned success=false: {}", errorMsg); @@ -174,12 +194,14 @@ class QbraidServerHelper : public ServerHelper { } } // v2 API: measurementCounts nested under data.resultData - else if (resultJson.contains("data") - && resultJson["data"].contains("resultData") - && resultJson["data"]["resultData"].contains("measurementCounts")) { + else if (resultJson.contains("data") && + resultJson["data"].contains("resultData") && + resultJson["data"]["resultData"].contains( + "measurementCounts")) { cudaq::info("Processing results from v2 endpoint"); CountsDictionary counts; - auto &measurements = resultJson["data"]["resultData"]["measurementCounts"]; + auto &measurements = + resultJson["data"]["resultData"]["measurementCounts"]; for (const auto &[bitstring, count] : measurements.items()) { counts[bitstring] = @@ -195,15 +217,20 @@ class QbraidServerHelper : public ServerHelper { // No valid data yet and no explicit error - retry if (attempt < maxRetries - 1) { - int sleepTime = (attempt == 0) ? waitTime : waitTime * std::pow(backoffFactor, attempt); - cudaq::info("No valid results yet, retrying in {} seconds", sleepTime); + int sleepTime = (attempt == 0) + ? waitTime + : waitTime * std::pow(backoffFactor, attempt); + cudaq::info("No valid results yet, retrying in {} seconds", + sleepTime); std::this_thread::sleep_for(std::chrono::seconds(sleepTime)); } } catch (const std::exception &e) { cudaq::info("Exception when fetching results: {}", e.what()); if (attempt < maxRetries - 1) { - int sleepTime = (attempt == 0) ? waitTime : waitTime * std::pow(backoffFactor, attempt); + int sleepTime = (attempt == 0) + ? waitTime + : waitTime * std::pow(backoffFactor, attempt); cudaq::info("Retrying in {} seconds", sleepTime); std::this_thread::sleep_for(std::chrono::seconds(sleepTime)); } @@ -234,8 +261,7 @@ class QbraidServerHelper : public ServerHelper { // Normalizing to a single register is the canonical QASM 2 form and is // accepted uniformly by every qBraid-reachable backend. std::string normalizeClassicalRegisters(const std::string &qasm) const { - static const std::regex cregDeclRx( - R"(creg\s+(\w+)\s*\[\s*(\d+)\s*\]\s*;)"); + static const std::regex cregDeclRx(R"(creg\s+(\w+)\s*\[\s*(\d+)\s*\]\s*;)"); std::vector> cregs; for (auto it = std::sregex_iterator(qasm.begin(), qasm.end(), cregDeclRx); @@ -263,7 +289,8 @@ class QbraidServerHelper : public ServerHelper { std::regex measureTargetRx("->\\s*" + name + "\\s*\\[\\s*" + std::to_string(i) + "\\s*\\]"); out = std::regex_replace(out, measureTargetRx, - "-> qbraid__creg__[" + std::to_string(base + i) + "]"); + "-> qbraid__creg__[" + + std::to_string(base + i) + "]"); } } @@ -280,14 +307,16 @@ class QbraidServerHelper : public ServerHelper { out = std::regex_replace(out, toRemove, ""); } - cudaq::info("Normalized {} classical registers into single qbraid__creg__[{}]", - cregs.size(), totalBits); + cudaq::info( + "Normalized {} classical registers into single qbraid__creg__[{}]", + cregs.size(), totalBits); return out; } RestHeaders getHeaders() override { if (backendConfig.find("api_key") == backendConfig.end()) { - throw std::runtime_error("API key not found in config. Was initialize() called?"); + throw std::runtime_error( + "API key not found in config. Was initialize() called?"); } RestHeaders headers; @@ -297,7 +326,8 @@ class QbraidServerHelper : public ServerHelper { return headers; } - std::string getEnvVar(const std::string &key, const std::string &defaultVal, const bool isRequired) const { + std::string getEnvVar(const std::string &key, const std::string &defaultVal, + const bool isRequired) const { const char *env_var = std::getenv(key.c_str()); if (env_var == nullptr) { if (isRequired) { diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp index 0d5b1fb1b09..7580ab62750 100644 --- a/unittests/backends/qbraid/QbraidTester.cpp +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -216,4 +216,4 @@ int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); return ret; -} \ No newline at end of file +} diff --git a/utils/mock_qpu/qbraid/__init__.py b/utils/mock_qpu/qbraid/__init__.py index 70686afd7c3..a98dabd9b10 100644 --- a/utils/mock_qpu/qbraid/__init__.py +++ b/utils/mock_qpu/qbraid/__init__.py @@ -113,23 +113,17 @@ def simulate_job(qasm: str, num_shots: int) -> dict[str, int]: distribution = random.choices(possible_states, k=num_shots) result = {state: distribution.count(state) for state in set(distribution)} - if ( - num_qubits == 2 - and len(measured_qubits) == 1 - and measured_qubits[0] == 0 - and 0 in superposition_qubits - ): + if (num_qubits == 2 and len(measured_qubits) == 1 and + measured_qubits[0] == 0 and 0 in superposition_qubits): new_result = {} total_shots = num_shots half_shots = total_shots // 2 - new_result["00"] = random.randint( - half_shots - half_shots // 4, half_shots + half_shots // 4 - ) + new_result["00"] = random.randint(half_shots - half_shots // 4, + half_shots + half_shots // 4) new_result["01"] = 0 - new_result["10"] = random.randint( - half_shots - half_shots // 4, half_shots + half_shots // 4 - ) + new_result["10"] = random.randint(half_shots - half_shots // 4, + half_shots + half_shots // 4) new_result["11"] = 0 remaining = total_shots - (new_result["00"] + new_result["10"]) @@ -163,7 +157,8 @@ def poll_job_status(job_id: str) -> dict[str, Any]: # v2 API: POST /jobs @app.post("/jobs") -async def postJob(job: Job, x_api_key: Optional[str] = Header(None, alias="X-API-KEY")): +async def postJob(job: Job, + x_api_key: Optional[str] = Header(None, alias="X-API-KEY")): """Submit a quantum job for execution (v2 API).""" if x_api_key is None: raise HTTPException(status_code=401, detail="API key is required") @@ -190,7 +185,13 @@ async def postJob(job: Job, x_api_key: Optional[str] = Header(None, alias="X-API JOBS_MOCK_RESULTS[newId] = counts # v2 response: wrapped in success/data envelope - return {"success": True, "data": {"jobQrn": newId, "status": "INITIALIZING"}} + return { + "success": True, + "data": { + "jobQrn": newId, + "status": "INITIALIZING" + } + } # Test-only: arm a failure for the next submitted job. @@ -210,8 +211,8 @@ async def armDelayResults(count: int = Path(...)): # v2 API: GET /jobs/{job_qrn} @app.get("/jobs/{job_id}") async def getJob( - job_id: str = Path(...), - x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), + job_id: str = Path(...), + x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), ): """Retrieve the status of a quantum job (v2 API).""" if x_api_key is None: @@ -226,8 +227,8 @@ async def getJob( # v2 API: GET /jobs/{job_qrn}/program @app.get("/jobs/{job_id}/program") async def getJobProgram( - job_id: str = Path(...), - x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), + job_id: str = Path(...), + x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), ): """Retrieve the program of a quantum job (v2 API).""" if x_api_key is None: @@ -251,8 +252,8 @@ async def getJobProgram( # v2 API: GET /jobs/{job_qrn}/result @app.get("/jobs/{job_id}/result") async def getJobResult( - job_id: str = Path(...), - x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), + job_id: str = Path(...), + x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), ): """Retrieve the results of a quantum job (v2 API).""" if x_api_key is None: @@ -263,14 +264,16 @@ async def getJobResult( if JOBS_MOCK_DB[job_id]["status"] in {"FAILED", "CANCELLED"}: raise HTTPException( - status_code=409, detail="Results unavailable. Job failed or was cancelled." - ) + status_code=409, + detail="Results unavailable. Job failed or was cancelled.") if JOBS_MOCK_DB[job_id]["status"] != "COMPLETED": # v2: use success=false instead of "error" field return { "success": False, - "data": {"status": JOBS_MOCK_DB[job_id]["status"]}, + "data": { + "status": JOBS_MOCK_DB[job_id]["status"] + }, } if job_id not in JOBS_MOCK_RESULTS: @@ -283,8 +286,10 @@ async def getJobResult( return { "success": False, "data": { - "status": "COMPLETED", - "message": "Failed to retrieve job results. Please wait, and try again.", + "status": + "COMPLETED", + "message": + "Failed to retrieve job results. Please wait, and try again.", }, } @@ -294,7 +299,9 @@ async def getJobResult( return { "success": True, "data": { - "resultData": {"measurementCounts": counts}, + "resultData": { + "measurementCounts": counts + }, "status": "COMPLETED", "cost": 0, "timeStamps": {}, From 1a24c665e7efe39285ad8f3b9a3516551eb15fd2 Mon Sep 17 00:00:00 2001 From: Harshit Date: Thu, 16 Apr 2026 14:30:08 +0000 Subject: [PATCH 07/18] fix: docs for qbraid helper and update examples --- .github/pre-commit/spelling_allowlist.txt | 4 +- docs/sphinx/targets/cpp/qbraid.cpp | 8 +- docs/sphinx/targets/python/qbraid.py | 8 +- docs/sphinx/using/backends/cloud.rst | 2 +- docs/sphinx/using/backends/cloud/qbraid.rst | 102 ++++++++++++------ .../using/backends/hardware/iontrap.rst | 63 ----------- .../{test_Qbraid.py => test_qbraid.py} | 3 +- .../helpers/qbraid/QbraidServerHelper.cpp | 8 +- 8 files changed, 87 insertions(+), 111 deletions(-) rename python/tests/backends/{test_Qbraid.py => test_qbraid.py} (99%) diff --git a/.github/pre-commit/spelling_allowlist.txt b/.github/pre-commit/spelling_allowlist.txt index 984c5e929c8..12d96e4e884 100644 --- a/.github/pre-commit/spelling_allowlist.txt +++ b/.github/pre-commit/spelling_allowlist.txt @@ -1,7 +1,6 @@ ABI AFQMC API -api APIs AST Aer @@ -125,7 +124,6 @@ QRMI QTX QX QaaS -Qbraid Qiskit QuEra QuTiP @@ -172,6 +170,7 @@ amongst ancilla ansatz ansatzes +api archiver arity auxillary @@ -338,6 +337,7 @@ preprocessor probability programmatically pybind +qBraid qaoa qbraid qed diff --git a/docs/sphinx/targets/cpp/qbraid.cpp b/docs/sphinx/targets/cpp/qbraid.cpp index b6d859ec2de..f7a15a0906e 100644 --- a/docs/sphinx/targets/cpp/qbraid.cpp +++ b/docs/sphinx/targets/cpp/qbraid.cpp @@ -2,12 +2,12 @@ // ``` // nvq++ --target qbraid qbraid.cpp -o out.x && ./out.x // ``` -// This will submit the job to the Qbraid ideal simulator target (default). +// This will submit the job to the qBraid ideal simulator target (default). #include #include -// Define a simple quantum kernel to execute on Qbraid. +// Define a simple quantum kernel to execute on qBraid. struct ghz { // Maximally entangled state between 5 qubits. auto operator()() __qpu__ { @@ -21,7 +21,7 @@ struct ghz { }; int main() { - // Submit to Qbraid asynchronously (e.g., continue executing + // Submit to qBraid asynchronously (e.g., continue executing // code in the file until the job has been returned). auto future = cudaq::sample_async(ghz{}); // ... classical code to execute in the meantime ... @@ -41,7 +41,7 @@ int main() { auto async_counts = readIn.get(); async_counts.dump(); - // OR: Submit to Qbraid synchronously (e.g., wait for the job + // OR: Submit to qBraid synchronously (e.g., wait for the job // result to be returned before proceeding). auto counts = cudaq::sample(ghz{}); counts.dump(); diff --git a/docs/sphinx/targets/python/qbraid.py b/docs/sphinx/targets/python/qbraid.py index cf3fe483c6b..dc61d605709 100644 --- a/docs/sphinx/targets/python/qbraid.py +++ b/docs/sphinx/targets/python/qbraid.py @@ -7,7 +7,7 @@ cudaq.set_target("qbraid") -# Create the kernel we'd like to execute on Qbraid. +# Create the kernel we'd like to execute on qBraid. @cudaq.kernel def kernel(): qvector = cudaq.qvector(2) @@ -15,12 +15,12 @@ def kernel(): x.ctrl(qvector[0], qvector[1]) -# Execute on Qbraid and print out the results. +# Execute on qBraid and print out the results. # Option A: # By using the asynchronous `cudaq.sample_async`, the remaining # classical code will be executed while the job is being handled -# by IonQ. This is ideal when submitting via a queue over +# by qBraid. This is ideal when submitting via a queue over # the cloud. async_results = cudaq.sample_async(kernel) # ... more classical code to run ... @@ -46,6 +46,6 @@ def kernel(): # Option B: # By using the synchronous `cudaq.sample`, the execution of # any remaining classical code in the file will occur only -# after the job has been returned from Qbraid. +# after the job has been returned from qBraid. counts = cudaq.sample(kernel) print(counts) diff --git a/docs/sphinx/using/backends/cloud.rst b/docs/sphinx/using/backends/cloud.rst index d2044d64e9e..ebd02e033e8 100644 --- a/docs/sphinx/using/backends/cloud.rst +++ b/docs/sphinx/using/backends/cloud.rst @@ -8,4 +8,4 @@ CUDA-Q provides a number of options to access hardware resources (GPUs and QPUs) Amazon Braket (braket) Scaleway QaaS (scaleway) - Qbraid + qBraid diff --git a/docs/sphinx/using/backends/cloud/qbraid.rst b/docs/sphinx/using/backends/cloud/qbraid.rst index a7e7fe4a2ae..dfa72e53913 100644 --- a/docs/sphinx/using/backends/cloud/qbraid.rst +++ b/docs/sphinx/using/backends/cloud/qbraid.rst @@ -1,61 +1,101 @@ -QBRAID -+++++++ +qBraid +++++++ .. _qbraid-backend: +`qBraid `__ is a cloud platform that brokers access to +quantum simulators and hardware from multiple vendors through a single API. +CUDA-Q can submit OpenQASM 2 jobs to any device exposed by the qBraid service. +See the `qBraid device catalog `__ for the +set of simulators and QPUs currently available. + Setting Credentials -````````````````````````` +``````````````````` -Programmers of CUDA-Q may access the `Qbraid Devices -`__ from either C++ or Python. Generate -an API key from your `Qbraid account `__ and export -it as an environment variable: +Generate an API key from your `qBraid account `__ +and export it as an environment variable: .. code:: bash - export QBRAID_API_KEY="qbraid_generated_api_key" + export QBRAID_API_KEY="qbraid_generated_api_key" + +Alternatively, the API key can be passed directly to ``cudaq.set_target`` via +the ``api_key`` argument (see below). + +Submitting +`````````` + +.. tab:: Python + + The target to which quantum kernels are submitted can be controlled with + the ``cudaq.set_target()`` function. + + .. code:: python + + cudaq.set_target("qbraid") + + By default, jobs are submitted to the qBraid state vector simulator + (``qbraid:qbraid:sim:qir-sv``). + To specify a different qBraid device, set the ``machine`` parameter to its + qBraid device ID. -Submission from Python -````````````````````````` + .. code:: python + + cudaq.set_target("qbraid", machine="qbraid:qbraid:sim:qir-sv") - First, set the :code:`qbraid` backend. + The API key can also be supplied inline instead of through the + ``QBRAID_API_KEY`` environment variable. .. code:: python - cudaq.set_target('qbraid') + cudaq.set_target("qbraid", api_key="qbraid_generated_api_key") - By default, quantum kernel code will be submitted to the IonQ simulator on qBraid. + qBraid devices are cloud-hosted, so local emulation via the ``emulate`` + flag is not supported — all jobs are executed on the qBraid service. + To run without submitting to real hardware, select one of the qBraid + simulator devices (for example, ``qbraid:qbraid:sim:qir-sv``) via the + ``machine`` argument. - To emulate the qbraid's simulator locally, without submitting through the cloud, you can also set the ``emulate`` flag to ``True``. This will emit any target specific compiler diagnostics. + The number of shots for a kernel execution can be set through the + ``shots_count`` argument to ``cudaq.sample`` or ``cudaq.observe``. The + default is 1000. - .. code:: python + .. code:: python - cudaq.set_target('qbraid', emulate=True) + cudaq.sample(kernel, shots_count=10000) - The number of shots for a kernel execution can be set through the ``shots_count`` argument to ``cudaq.sample`` or ``cudaq.observe``. By default, the ``shots_count`` is set to 1000. +.. tab:: C++ - .. code:: python + To target quantum kernel code for execution on qBraid, pass the flag + ``--target qbraid`` to the ``nvq++`` compiler. By default jobs are + submitted to the qBraid state vector simulator + (``qbraid:qbraid:sim:qir-sv``). - cudaq.sample(kernel, shots_count=10000) + .. code:: bash - To see a complete example for using Qbraid's backends, take a look at our :doc:`Python examples <../../examples/examples>`. + nvq++ --target qbraid src.cpp -Submission from C++ -````````````````````````` - To target quantum kernel code for execution using qbraid, - pass the flag ``--target qbraid`` to the ``nvq++`` compiler. + To execute kernels on a different device, pass ``--qbraid-machine`` with + the qBraid device ID: - .. code:: bash + .. code:: bash - nvq++ --target qbraid src.cpp + nvq++ --target qbraid --qbraid-machine "qbraid:qbraid:sim:qir-sv" src.cpp - This will take the API key and handle all authentication with, and submission to, the Qbraid device. By default, quantum kernel code will be submitted to the Qbraidsimulator. + The API key can be passed explicitly with ``--qbraid-api_key`` instead of + being read from ``QBRAID_API_KEY``: - To emulate the qbraid's machine locally, without submitting through the cloud, you can also pass the ``--emulate`` flag to ``nvq++``. This will emit any target specific compiler diagnostics, before running a noise free emulation. + .. code:: bash - .. code:: bash + nvq++ --target qbraid --qbraid-api_key "qbraid_generated_api_key" src.cpp - nvq++ --emulate --target qbraid src.cpp + qBraid devices are cloud-hosted, so the ``--emulate`` flag is not + supported for this target — all jobs are executed on the qBraid + service. To run without submitting to real hardware, pass + ``--qbraid-machine`` with a qBraid simulator device ID (for example, + ``qbraid:qbraid:sim:qir-sv``). - To see a complete example for using IonQ's backends, take a look at our :doc:`C++ examples <../../examples/examples>`. +To see a complete example for using qBraid's backends, take a look at our +:doc:`Python examples <../../examples/examples>` and +:doc:`C++ examples <../../examples/examples>`. diff --git a/docs/sphinx/using/backends/hardware/iontrap.rst b/docs/sphinx/using/backends/hardware/iontrap.rst index 0dc69de2177..160ab9f549f 100644 --- a/docs/sphinx/using/backends/hardware/iontrap.rst +++ b/docs/sphinx/using/backends/hardware/iontrap.rst @@ -245,66 +245,3 @@ To see a complete example, take a look at :ref:`Quantinuum examples `__ from either C++ or Python. Generate -an API key from your `Qbraid account `__ and export -it as an environment variable: - -.. code:: bash - - export QBRAID_API_KEY="qbraid_generated_api_key" - - -Submitting -````````````````````````` -.. tab:: Python - - First, set the :code:`qbraid` backend. - - .. code:: python - - cudaq.set_target('qbraid') - - By default, quantum kernel code will be submitted to the IonQ simulator on qBraid. - - To emulate the qbraid's simulator locally, without submitting through the cloud, you can also set the ``emulate`` flag to ``True``. This will emit any target specific compiler diagnostics. - - .. code:: python - - cudaq.set_target('qbraid', emulate=True) - - The number of shots for a kernel execution can be set through the ``shots_count`` argument to ``cudaq.sample`` or ``cudaq.observe``. By default, the ``shots_count`` is set to 1000. - - .. code:: python - - cudaq.sample(kernel, shots_count=10000) - - To see a complete example for using Qbraid's backends, take a look at our :doc:`Python examples <../../examples/examples>`. - - -.. tab:: C++ - - To target quantum kernel code for execution using qbraid, - pass the flag ``--target qbraid`` to the ``nvq++`` compiler. - - .. code:: bash - - nvq++ --target qbraid src.cpp - - This will take the API key and handle all authentication with, and submission to, the Qbraid device. By default, quantum kernel code will be submitted to the Qbraidsimulator. - - To emulate the qbraid's machine locally, without submitting through the cloud, you can also pass the ``--emulate`` flag to ``nvq++``. This will emit any target specific compiler diagnostics, before running a noise free emulation. - - .. code:: bash - - nvq++ --emulate --target qbraid src.cpp - - To see a complete example for using IonQ's backends, take a look at our :doc:`C++ examples <../../examples/examples>`. diff --git a/python/tests/backends/test_Qbraid.py b/python/tests/backends/test_qbraid.py similarity index 99% rename from python/tests/backends/test_Qbraid.py rename to python/tests/backends/test_qbraid.py index 6b6d1599753..c42cc3e1a02 100644 --- a/python/tests/backends/test_Qbraid.py +++ b/python/tests/backends/test_qbraid.py @@ -23,10 +23,9 @@ port = 62452 # Default machine for tests. Mirrors the real qBraid device string format. -TEST_MACHINE = "ionq:ionq:sim:simulator" +TEST_MACHINE = "qbraid:qbraid:sim:qir-sv" TEST_API_KEY = "00000000000000000000000000000000" - # The qbraid mock server in utils/mock_qpu/qbraid/__init__.py doesn't simulate # quantum mechanics - it only inspects the QASM for `h` and `measure` ops and # generates random outcomes for qubits with H. It does NOT model entanglement diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index b53979fd98e..a9c4e1a8e6d 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -19,14 +19,14 @@ namespace cudaq { class QbraidServerHelper : public ServerHelper { static constexpr const char *DEFAULT_URL = "https://api-v2.qbraid.com/api/v1"; - static constexpr const char *DEFAULT_DEVICE = "ionq:ionq:sim:simulator"; + static constexpr const char *DEFAULT_DEVICE = "qbraid:qbraid:sim:qir-sv"; static constexpr int DEFAULT_QUBITS = 29; public: const std::string name() const override { return "qbraid"; } void initialize(BackendConfig config) override { - cudaq::info("Initializing Qbraid Backend."); + cudaq::info("Initializing qBraid Backend."); backendConfig.clear(); backendConfig["url"] = getValueOrDefault(config, "url", DEFAULT_URL); @@ -34,7 +34,7 @@ class QbraidServerHelper : public ServerHelper { backendConfig["qubits"] = std::to_string(DEFAULT_QUBITS); // Accept "machine" as a user-friendly alias for device_id - // Usage: cudaq.set_target("qbraid", machine="ionq:ionq:sim:simulator") + // Usage: cudaq.set_target("qbraid", machine="qbraid:qbraid:sim:qir-sv") if (!config["machine"].empty()) { backendConfig["device_id"] = config["machine"]; } else { @@ -61,7 +61,7 @@ class QbraidServerHelper : public ServerHelper { parseConfigForCommonParams(config); - cudaq::info("Qbraid configuration initialized:"); + cudaq::info("qBraid configuration initialized:"); for (const auto &[key, value] : backendConfig) { cudaq::info(" {} = {}", key, value); } From 31bde9eb8c7296451e84ce09fa33b434b86920af Mon Sep 17 00:00:00 2001 From: Harshit Date: Fri, 17 Apr 2026 06:24:52 +0000 Subject: [PATCH 08/18] DCO Remediation Commit for Harshit I, Harshit , hereby add my Signed-off-by to this commit: 9cd62cffc6562aef4c425857cb97edf80c5a3407 I, Harshit , hereby add my Signed-off-by to this commit: 3b0a1e4c84bab1378b4b37b279060143794cb21f I, Harshit , hereby add my Signed-off-by to this commit: 1a24c665e7efe39285ad8f3b9a3516551eb15fd2 Signed-off-by: Harshit --- .../platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index a9c4e1a8e6d..a7f89cbb0b7 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -33,7 +33,7 @@ class QbraidServerHelper : public ServerHelper { backendConfig["user_agent"] = "cudaq/" + std::string(cudaq::getVersion()); backendConfig["qubits"] = std::to_string(DEFAULT_QUBITS); - // Accept "machine" as a user-friendly alias for device_id + // Accept "machine" as a user-friendly alias for qBraid's device_id // Usage: cudaq.set_target("qbraid", machine="qbraid:qbraid:sim:qir-sv") if (!config["machine"].empty()) { backendConfig["device_id"] = config["machine"]; From 61e4b91db34eebf5ab0b25a9cc6fc8e730d85c38 Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Fri, 17 Apr 2026 06:51:14 +0000 Subject: [PATCH 09/18] DCO Remediation Commit for TheGupta2012 I, TheGupta2012 , hereby add my Signed-off-by to this commit: 925ae39eebd02886afd9415a9546b1f74fc65d15 I, TheGupta2012 , hereby add my Signed-off-by to this commit: 41fe2486fc9777cd8941ec1d4f4df5da8a6cf389 I, TheGupta2012 , hereby add my Signed-off-by to this commit: d74243dd59e94091acae92bedd96ae41332bde68 Signed-off-by: TheGupta2012 --- .../platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index a7f89cbb0b7..1b137c03bed 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -254,7 +254,7 @@ class QbraidServerHelper : public ServerHelper { // // Context: nvq++ emits one `creg varK[1];` per measurement. AWS Braket's // classical simulators (SV1, DM1, TN1) tolerate that via lenient register - // concatenation, but stricter hardware transpilers reject it: + // concatenation, but stricter hardware transpilers below reject it: // - IQM (Garnet etc.): returns only the first register -> 1-bit results // - Rigetti: collapses all registers onto b[0] -> "bit already in use" // - IonQ-via-Braket: similar strict behavior From f6ba81084db4cdf7f651577c0b0b618a31dd9b80 Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Mon, 20 Apr 2026 11:51:35 +0000 Subject: [PATCH 10/18] fix: formatting issues, api key leak and default num of qubits Signed-off-by: TheGupta2012 --- .github/workflows/integration_tests.yml | 26 +++++----- .../using/backends/hardware/iontrap.rst | 47 +++++++++---------- .../helpers/qbraid/QbraidServerHelper.cpp | 8 +++- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 508ed712532..32d938209fd 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -10,9 +10,9 @@ on: workflow_dispatch: inputs: target: - description: "Target (choose nightly to run like nightly tests)" + description: 'Target (choose nightly to run like nightly tests)' required: true - default: "nightly" + default: 'nightly' type: choice options: - nightly @@ -32,38 +32,38 @@ on: single_test_name: type: string required: false - description: "Single test (e.g., targettests/quantinuum/load_value.cpp). Runs default tests if left blank" + description: 'Single test (e.g., targettests/quantinuum/load_value.cpp). Runs default tests if left blank' target_machine: type: string required: false - description: "Target machine (e.g., H2-1E)." + description: 'Target machine (e.g., H2-1E).' cudaq_test_image: type: string required: false - default: "" # picked up from repo variable if not provided - description: "CUDA Quantum image to run the tests in. Default to the latest CUDA Quantum nightly image" + default: '' # picked up from repo variable if not provided + description: 'CUDA Quantum image to run the tests in. Default to the latest CUDA Quantum nightly image' commit_sha: type: string required: false - description: "Commit SHA to pull the code (examples/tests) for testing. Default to the commit associated with the CUDA Quantum docker image if left blank" + description: 'Commit SHA to pull the code (examples/tests) for testing. Default to the commit associated with the CUDA Quantum docker image if left blank' workflow_id: type: string required: false - description: "Workflow Id to retrieve the Python wheel for testing. Default to the wheels produced by the Publishing workflow associated with the latest nightly CUDA Quantum Docker image if left blank" + description: 'Workflow Id to retrieve the Python wheel for testing. Default to the wheels produced by the Publishing workflow associated with the latest nightly CUDA Quantum Docker image if left blank' python_version: type: choice required: true - description: "Python version to run wheel test" + description: 'Python version to run wheel test' options: - - "3.11" - - "3.12" - - "3.13" + - '3.11' + - '3.12' + - '3.13' schedule: - cron: 0 3 * * * env: - python_version: "3.12" + python_version: '3.12' jobs: # Run a daily check of all links in the docs to find any newly broken links diff --git a/docs/sphinx/using/backends/hardware/iontrap.rst b/docs/sphinx/using/backends/hardware/iontrap.rst index 160ab9f549f..83e25326455 100644 --- a/docs/sphinx/using/backends/hardware/iontrap.rst +++ b/docs/sphinx/using/backends/hardware/iontrap.rst @@ -31,7 +31,7 @@ Submitting By default, quantum kernel code will be submitted to the IonQ simulator. - .. note:: + .. note:: A "target" in :code:`cudaq` refers to a quantum compute provider, such as :code:`ionq`. However, IonQ's documentation uses the term "target" to refer to specific QPU's themselves. @@ -70,7 +70,7 @@ Submitting This will take the API key and handle all authentication with, and submission to, the IonQ QPU(s). By default, quantum kernel code will be submitted to the IonQsimulator. - .. note:: + .. note:: A "target" in :code:`cudaq` refers to a quantum compute provider, such as :code:`ionq`. However, IonQ's documentation uses the term "target" to refer to specific QPU's themselves. @@ -105,7 +105,7 @@ Setting Credentials ``````````````````` Programmers of CUDA-Q may access the Quantinuum API from either -C++ or Python. Quantinuum requires a credential configuration file. +C++ or Python. Quantinuum requires a credential configuration file. The configuration file can be generated as follows, replacing the ``email`` and ``credentials`` in the first line with your Quantinuum account details. @@ -134,8 +134,8 @@ Create a project in the Nexus portal. You can find the project ID in the URL of .. tab:: Python - - The backend to which quantum kernels are submitted + + The backend to which quantum kernels are submitted can be controlled with the ``cudaq.set_target()`` function. .. code:: python @@ -155,15 +155,15 @@ Create a project in the Nexus portal. You can find the project ID in the URL of cudaq.set_target('quantinuum', machine='H2-2') where ``H2-2`` is an example of a physical QPU. Hardware specific - emulators may be accessed by appending an ``E`` to the end (e.g, ``H2-2E``). For - access to the syntax checker for the provided machine, you may append an ``SC`` + emulators may be accessed by appending an ``E`` to the end (e.g, ``H2-2E``). For + access to the syntax checker for the provided machine, you may append an ``SC`` to the end (e.g, ``H2-1SC``). - For a comprehensive list of available machines, login to your `Quantinuum Nexus user account `__ + For a comprehensive list of available machines, login to your `Quantinuum Nexus user account `__ and navigate to the "Profile" tab, where you should find a table titled "Quantinuum Systems Access". To emulate the Quantinuum machine locally, without submitting through the cloud, - you can set the ``emulate`` flag to ``True``. This will emit any target + you can set the ``emulate`` flag to ``True``. This will emit any target specific compiler warnings and diagnostics, before running a noise free emulation. You do not need to specify project or machine when emulating. @@ -175,7 +175,7 @@ Create a project in the Nexus portal. You can find the project ID in the URL of the ``shots_count`` argument to ``cudaq.sample`` or ``cudaq.observe``. By default, the ``shots_count`` is set to 1000. - .. code:: python + .. code:: python cudaq.sample(kernel, shots_count=10000) @@ -183,7 +183,7 @@ Create a project in the Nexus portal. You can find the project ID in the URL of .. tab:: C++ To target quantum kernel code for execution in the Quantinuum backends, - pass the flag ``--target quantinuum`` to the ``nvq++`` compiler. CUDA-Q will + pass the flag ``--target quantinuum`` to the ``nvq++`` compiler. CUDA-Q will authenticate via the Quantinuum REST API using the credential in your configuration file. By default, quantum kernel code will be submitted to the Quantinuum syntax checker. Submission to the syntax checker merely validates the program; the kernels are not executed. @@ -202,15 +202,15 @@ Create a project in the Nexus portal. You can find the project ID in the URL of nvq++ --target quantinuum --quantinuum-machine H2-2 src.cpp ... where ``H2-2`` is an example of a physical QPU. Hardware specific - emulators may be accessed by appending an ``E`` to the end (e.g, ``H2-2E``). For - access to the syntax checker for the provided machine, you may append an ``SC`` + emulators may be accessed by appending an ``E`` to the end (e.g, ``H2-2E``). For + access to the syntax checker for the provided machine, you may append an ``SC`` to the end (e.g, ``H2-1SC``). - For a comprehensive list of available machines, login to your `Quantinuum Nexus user account `__ + For a comprehensive list of available machines, login to your `Quantinuum Nexus user account `__ and navigate to the "Profile" tab, where you should find a table titled "Quantinuum Systems Access". To emulate the Quantinuum machine locally, without submitting through the cloud, - you can pass the ``--emulate`` flag to ``nvq++``. This will emit any target + you can pass the ``--emulate`` flag to ``nvq++``. This will emit any target specific compiler warnings and diagnostics, before running a noise free emulation. You do not need to specify project or machine when emulating. @@ -218,15 +218,15 @@ Create a project in the Nexus portal. You can find the project ID in the URL of nvq++ --emulate --target quantinuum src.cpp -.. note:: +.. note:: Quantinuum's syntax checker for Helios (e.g., ``Helios-1SC``) only performs QIR code validation and does not return any results. Thus, it always returns an empty result set. This is different from other Quantinuum backends (e.g., ``H2-1SC``) where the syntax checker returns dummy results. As a result, when using the Helios syntax checker, we may receive this warning message: .. code:: text - - WARNING: this kernel invocation produced 0 shots worth of results when executed. + + WARNING: this kernel invocation produced 0 shots worth of results when executed. It means that the kernel was successfully validated, but no execution results are available. To get results, please submit to the Helios emulator (e.g., ``Helios-1E``) or the actual quantum device (e.g., ``Helios-1``). @@ -235,13 +235,12 @@ Create a project in the Nexus portal. You can find the project ID in the URL of To see a complete example, take a look at :ref:`Quantinuum examples `. -.. note:: +.. note:: In local emulation mode (``emulate`` flag set to ``True``), the program will be executed on the :ref:`default simulator `. - The environment variable ``CUDAQ_DEFAULT_SIMULATOR`` can be used to change the emulation simulator. - + The environment variable ``CUDAQ_DEFAULT_SIMULATOR`` can be used to change the emulation simulator. + For example, the simulation floating point accuracy and/or the simulation capabilities (e.g., maximum number of qubits, supported quantum gates), - depend on the selected simulator. - + depend on the selected simulator. + Any environment variables must be set prior to setting the target or running "`import cudaq`". - diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index 1b137c03bed..75adbb12594 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -20,7 +20,7 @@ namespace cudaq { class QbraidServerHelper : public ServerHelper { static constexpr const char *DEFAULT_URL = "https://api-v2.qbraid.com/api/v1"; static constexpr const char *DEFAULT_DEVICE = "qbraid:qbraid:sim:qir-sv"; - static constexpr int DEFAULT_QUBITS = 29; + static constexpr int DEFAULT_QUBITS = 30; public: const std::string name() const override { return "qbraid"; } @@ -63,7 +63,11 @@ class QbraidServerHelper : public ServerHelper { cudaq::info("qBraid configuration initialized:"); for (const auto &[key, value] : backendConfig) { - cudaq::info(" {} = {}", key, value); + if (key == "api_key") { + cudaq::info(" api_key = ", value.size()); + } else { + cudaq::info(" {} = {}", key, value); + } } } From f4907ac231e02f2fb991f9decbd75118131ee89a Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Mon, 20 Apr 2026 12:03:51 +0000 Subject: [PATCH 11/18] fix: headers to use 2026 Signed-off-by: TheGupta2012 --- .../cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt | 2 +- runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml | 2 +- targettests/qbraid/bug_qubit.cpp | 2 +- targettests/qbraid/callable_kernel_arg.cpp | 2 +- targettests/qbraid/cudaq_observe.cpp | 2 +- targettests/qbraid/if_jit.cpp | 2 +- targettests/qbraid/load_value.cpp | 2 +- targettests/qbraid/sudoku_2x2-1.cpp | 2 +- targettests/qbraid/sudoku_2x2-bit_names.cpp | 2 +- targettests/qbraid/sudoku_2x2-reg_name.cpp | 2 +- targettests/qbraid/sudoku_2x2.cpp | 2 +- targettests/qbraid/swap_gate.cpp | 2 +- targettests/qbraid/test-int8_t.cpp | 2 +- targettests/qbraid/test-int8_t_free_func.cpp | 2 +- targettests/qbraid/variable_size_qreg.cpp | 2 +- unittests/backends/qbraid/CMakeLists.txt | 2 +- unittests/backends/qbraid/QbraidStartServerAndTest.sh.in | 2 +- unittests/backends/qbraid/QbraidTester.cpp | 2 +- utils/mock_qpu/qbraid/__init__.py | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt index 823c01fd100..dac742b6824 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/CMakeLists.txt @@ -1,5 +1,5 @@ # ============================================================================ # -# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. # # All rights reserved. # # # # This source code and the accompanying materials are made available under # diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml index 0ee345afd43..da3f92dc94d 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml @@ -1,5 +1,5 @@ # ============================================================================ # -# Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. # +# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. # # All rights reserved. # # # # This source code and the accompanying materials are made available under # diff --git a/targettests/qbraid/bug_qubit.cpp b/targettests/qbraid/bug_qubit.cpp index 2179c9f4da1..2f53f71dd06 100644 --- a/targettests/qbraid/bug_qubit.cpp +++ b/targettests/qbraid/bug_qubit.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/callable_kernel_arg.cpp b/targettests/qbraid/callable_kernel_arg.cpp index 759469537e7..afa5fd8d960 100644 --- a/targettests/qbraid/callable_kernel_arg.cpp +++ b/targettests/qbraid/callable_kernel_arg.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/cudaq_observe.cpp b/targettests/qbraid/cudaq_observe.cpp index d9d1c537d85..0415480c32e 100644 --- a/targettests/qbraid/cudaq_observe.cpp +++ b/targettests/qbraid/cudaq_observe.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/if_jit.cpp b/targettests/qbraid/if_jit.cpp index 5719dc5b770..2b763a955ff 100644 --- a/targettests/qbraid/if_jit.cpp +++ b/targettests/qbraid/if_jit.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/load_value.cpp b/targettests/qbraid/load_value.cpp index ab5d9cec62e..6ed611e0685 100644 --- a/targettests/qbraid/load_value.cpp +++ b/targettests/qbraid/load_value.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/sudoku_2x2-1.cpp b/targettests/qbraid/sudoku_2x2-1.cpp index cd028025a0c..952d93495f6 100644 --- a/targettests/qbraid/sudoku_2x2-1.cpp +++ b/targettests/qbraid/sudoku_2x2-1.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/sudoku_2x2-bit_names.cpp b/targettests/qbraid/sudoku_2x2-bit_names.cpp index ef53021b359..e0de3cdd4da 100644 --- a/targettests/qbraid/sudoku_2x2-bit_names.cpp +++ b/targettests/qbraid/sudoku_2x2-bit_names.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/sudoku_2x2-reg_name.cpp b/targettests/qbraid/sudoku_2x2-reg_name.cpp index 6200c1070f7..eb42663a5bc 100644 --- a/targettests/qbraid/sudoku_2x2-reg_name.cpp +++ b/targettests/qbraid/sudoku_2x2-reg_name.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/sudoku_2x2.cpp b/targettests/qbraid/sudoku_2x2.cpp index e3d4bc2c0c3..a5cb54c0843 100644 --- a/targettests/qbraid/sudoku_2x2.cpp +++ b/targettests/qbraid/sudoku_2x2.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/swap_gate.cpp b/targettests/qbraid/swap_gate.cpp index 4f37edae871..7331ecbd262 100644 --- a/targettests/qbraid/swap_gate.cpp +++ b/targettests/qbraid/swap_gate.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/test-int8_t.cpp b/targettests/qbraid/test-int8_t.cpp index 7178f6c57bb..7f3cf8b63c3 100644 --- a/targettests/qbraid/test-int8_t.cpp +++ b/targettests/qbraid/test-int8_t.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/test-int8_t_free_func.cpp b/targettests/qbraid/test-int8_t_free_func.cpp index ca9db25ec6c..6c3eea548d8 100644 --- a/targettests/qbraid/test-int8_t_free_func.cpp +++ b/targettests/qbraid/test-int8_t_free_func.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/targettests/qbraid/variable_size_qreg.cpp b/targettests/qbraid/variable_size_qreg.cpp index 1f6c139a085..120fcede939 100644 --- a/targettests/qbraid/variable_size_qreg.cpp +++ b/targettests/qbraid/variable_size_qreg.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/unittests/backends/qbraid/CMakeLists.txt b/unittests/backends/qbraid/CMakeLists.txt index 390d20cc896..e088f6546de 100644 --- a/unittests/backends/qbraid/CMakeLists.txt +++ b/unittests/backends/qbraid/CMakeLists.txt @@ -1,5 +1,5 @@ # ============================================================================ # -# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. # # All rights reserved. # # # # This source code and the accompanying materials are made available under # diff --git a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in index bd5c15b9af7..3510a3077f1 100644 --- a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in +++ b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in @@ -1,7 +1,7 @@ #!/bin/bash # ============================================================================ # -# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. # # All rights reserved. # # # # This source code and the accompanying materials are made available under # diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp index 7580ab62750..37e99325139 100644 --- a/unittests/backends/qbraid/QbraidTester.cpp +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * diff --git a/utils/mock_qpu/qbraid/__init__.py b/utils/mock_qpu/qbraid/__init__.py index a98dabd9b10..20a559fff7c 100644 --- a/utils/mock_qpu/qbraid/__init__.py +++ b/utils/mock_qpu/qbraid/__init__.py @@ -1,5 +1,5 @@ # ============================================================================ # -# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. # # All rights reserved. # # # # This source code and the accompanying materials are made available under # From a79cc61bc596b940b8bffe7f10cf5f6e448ab88b Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Tue, 21 Apr 2026 06:47:12 +0000 Subject: [PATCH 12/18] fix: cmake list for qbraid to unconditionally include qbraid in the build and skip remote tests for qbraid Signed-off-by: TheGupta2012 --- runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt | 4 +--- scripts/validate_pycudaq.sh | 3 ++- unittests/backends/CMakeLists.txt | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt index 4574b6ba8fe..55fff380909 100644 --- a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt @@ -27,6 +27,4 @@ endif() if(CUDAQ_ENABLE_TII_BACKEND) add_subdirectory(tii) endif() -if(CUDAQ_ENABLE_QBRAID_BACKEND) - add_subdirectory(qbraid) -endif() +add_subdirectory(qbraid) diff --git a/scripts/validate_pycudaq.sh b/scripts/validate_pycudaq.sh index 738e3b4da46..2197dd05b81 100644 --- a/scripts/validate_pycudaq.sh +++ b/scripts/validate_pycudaq.sh @@ -472,7 +472,8 @@ if [ -d "$root_folder/targets" ]; then skip_example=true elif [ "$t" == "tii" ] || [ "$t" == "scaleway" ] || [ "$t" == "quantum_machines" ] || \ [ "$t" == "quantinuum" ] || [ "$t" == "orca" ] || [ "$t" == "orca-photonics" ] || \ - [ "$t" == "iqm" ] || [ "$t" == "infleqtion" ] || [ "$t" == "anyon" ]; then + [ "$t" == "iqm" ] || [ "$t" == "infleqtion" ] || [ "$t" == "anyon" ] || \ + [ "$t" == "qbraid" ]; then echo "Skipping $ex (remote target '$t' not available)" >&2 skip_example=true fi diff --git a/unittests/backends/CMakeLists.txt b/unittests/backends/CMakeLists.txt index 627ae6a7395..130fc4ecb0b 100644 --- a/unittests/backends/CMakeLists.txt +++ b/unittests/backends/CMakeLists.txt @@ -97,9 +97,7 @@ if (OPENSSL_FOUND AND CUDAQ_ENABLE_PYTHON AND CUDAQ_TEST_MOCK_SERVERS) if (CUDAQ_ENABLE_SCALEWAY_BACKEND) add_subdirectory(scaleway) endif() - if (CUDAQ_ENABLE_QBRAID_BACKEND) - add_subdirectory(qbraid) - endif() + add_subdirectory(qbraid) add_subdirectory(extra_payload_provider) add_subdirectory(quake_backend) endif() From 38830db80134c75fa1a16302a2c81de8914bc951 Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Tue, 21 Apr 2026 07:32:48 +0000 Subject: [PATCH 13/18] fix: target tests for qbraid to use execution test pattern and make apiKey target param depend on emulate flag Signed-off-by: TheGupta2012 --- .../helpers/qbraid/QbraidServerHelper.cpp | 71 ++++++++---- targettests/execution/bug_qubit.cpp | 1 + targettests/execution/callable_kernel_arg.cpp | 1 + targettests/execution/cudaq_observe.cpp | 1 + targettests/execution/if_jit.cpp | 1 + targettests/execution/int8_t.cpp | 1 + targettests/execution/int8_t_free_func.cpp | 1 + targettests/execution/load_value.cpp | 1 + targettests/execution/sudoku_2x2-1.cpp | 1 + targettests/execution/sudoku_2x2-bit_name.cpp | 1 + targettests/execution/sudoku_2x2-reg_name.cpp | 1 + targettests/execution/sudoku_2x2.cpp | 1 + targettests/execution/swap_gate.cpp | 1 + targettests/execution/variable_size_qreg.cpp | 1 + targettests/qbraid/bug_qubit.cpp | 44 +------- targettests/qbraid/callable_kernel_arg.cpp | 44 +------- targettests/qbraid/cudaq_observe.cpp | 51 +-------- targettests/qbraid/if_jit.cpp | 39 +------ targettests/qbraid/int8_t.cpp | 10 ++ targettests/qbraid/int8_t_free_func.cpp | 10 ++ targettests/qbraid/load_value.cpp | 57 +--------- targettests/qbraid/sudoku_2x2-1.cpp | 73 +------------ targettests/qbraid/sudoku_2x2-bit_name.cpp | 10 ++ targettests/qbraid/sudoku_2x2-bit_names.cpp | 103 ------------------ targettests/qbraid/sudoku_2x2-reg_name.cpp | 73 +------------ targettests/qbraid/sudoku_2x2.cpp | 72 +----------- targettests/qbraid/swap_gate.cpp | 37 +------ targettests/qbraid/test-int8_t.cpp | 48 -------- targettests/qbraid/test-int8_t_free_func.cpp | 46 -------- targettests/qbraid/variable_size_qreg.cpp | 40 +------ 30 files changed, 112 insertions(+), 729 deletions(-) create mode 100644 targettests/qbraid/int8_t.cpp create mode 100644 targettests/qbraid/int8_t_free_func.cpp create mode 100644 targettests/qbraid/sudoku_2x2-bit_name.cpp delete mode 100644 targettests/qbraid/sudoku_2x2-bit_names.cpp delete mode 100644 targettests/qbraid/test-int8_t.cpp delete mode 100644 targettests/qbraid/test-int8_t_free_func.cpp diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index 75adbb12594..e9ec8b46e5f 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -17,14 +17,20 @@ namespace cudaq { +/// @brief The QbraidServerHelper class extends the ServerHelper class to +/// handle interactions with the qBraid server for submitting and retrieving +/// quantum computation jobs to various qBraid supported devices. class QbraidServerHelper : public ServerHelper { static constexpr const char *DEFAULT_URL = "https://api-v2.qbraid.com/api/v1"; static constexpr const char *DEFAULT_DEVICE = "qbraid:qbraid:sim:qir-sv"; static constexpr int DEFAULT_QUBITS = 30; public: + /// @brief Returns the name of the server helper. const std::string name() const override { return "qbraid"; } + /// @brief Initializes the server helper with the provided backend + /// configuration. void initialize(BackendConfig config) override { cudaq::info("Initializing qBraid Backend."); @@ -44,10 +50,17 @@ class QbraidServerHelper : public ServerHelper { // Accept api_key from target arguments, fall back to QBRAID_API_KEY env var // Usage: cudaq.set_target("qbraid", api_key="my-key") + bool isApiKeyRequired = [&]() { + auto it = config.find("emulate"); + if (it != config.end() && it->second == "true") + return false; + return true; + }(); if (!config["api_key"].empty()) { backendConfig["api_key"] = config["api_key"]; } else { - backendConfig["api_key"] = getEnvVar("QBRAID_API_KEY", "", true); + backendConfig["api_key"] = + getEnvVar("QBRAID_API_KEY", "", isApiKeyRequired); } backendConfig["job_path"] = backendConfig["url"] + "/jobs"; @@ -71,6 +84,8 @@ class QbraidServerHelper : public ServerHelper { } } + /// @brief Creates a quantum computation job using the provided kernel + /// executions and returns the corresponding payload. ServerJobPayload createJob(std::vector &circuitCodes) override { if (backendConfig.find("job_path") == backendConfig.end()) { @@ -102,6 +117,7 @@ class QbraidServerHelper : public ServerHelper { return std::make_tuple(backendConfig.at("job_path"), getHeaders(), jobs); } + /// @brief Extracts the job ID from the server's response to a job submission. std::string extractJobId(ServerMessage &postResponse) override { // v2 API: jobQrn is nested under data envelope if (postResponse.contains("data") && @@ -112,6 +128,8 @@ class QbraidServerHelper : public ServerHelper { "ServerMessage doesn't contain 'data.jobQrn' key."); } + /// @brief Constructs the URL for retrieving a job based on the server's + /// response to a job submission. std::string constructGetJobPath(ServerMessage &postResponse) override { // v2 API: use path parameter instead of query parameter if (postResponse.contains("data") && @@ -123,16 +141,21 @@ class QbraidServerHelper : public ServerHelper { "ServerMessage doesn't contain 'data.jobQrn' key."); } + /// @brief Constructs the URL for retrieving a job based on a job ID. std::string constructGetJobPath(std::string &jobId) override { // v2 API: /jobs/{jobQrn} return backendConfig.at("job_path") + "/" + jobId; } + /// @brief Constructs the URL for retrieving the measurement results of a + /// completed job based on a job ID. std::string constructGetResultsPath(const std::string &jobId) { // v2 API: /jobs/{jobQrn}/result return backendConfig.at("job_path") + "/" + jobId + "/result"; } + /// @brief Checks if a job is done based on the server's response to a job + /// retrieval request. bool jobIsDone(ServerMessage &getJobResponse) override { std::string status; @@ -157,17 +180,16 @@ class QbraidServerHelper : public ServerHelper { return false; } - // Fetch results from v2 results endpoint with retry logic. - // - // Rationale: qbraid's v2 API has a window where status transitions to - // COMPLETED before the result payload is queryable on /result, so /result - // returns {success: false, data: {message: "not yet available"}}. The retry - // with backoff absorbs that race. - // - // Exercised deterministically via the mock's POST /test/delay_next_results - // endpoint (see checkResultRetry / checkResultRetryExhaustion tests). + /// @brief Processes the server's response to a job retrieval request and + /// maps the results back to sample results. cudaq::sample_result processResults(ServerMessage &getJobResponse, std::string &jobId) override { + // qbraid's v2 API has a window where status transitions to COMPLETED + // before the result payload is queryable on /result, so /result returns + // {success: false, data: {message: "not yet available"}}. Retry with + // backoff absorbs that race. Exercised deterministically via the mock's + // POST /test/delay_next_results endpoint (see checkResultRetry / + // checkResultRetryExhaustion tests). const int maxRetries = 3; const int waitTime = 2; const float backoffFactor = 2.0; @@ -252,19 +274,18 @@ class QbraidServerHelper : public ServerHelper { } private: - // Merge multiple single-bit classical registers emitted by nvq++'s QASM 2 - // codegen into a single multi-bit `creg c[N]`. This is required to unblock - // qBraid-routed hardware backends. - // - // Context: nvq++ emits one `creg varK[1];` per measurement. AWS Braket's - // classical simulators (SV1, DM1, TN1) tolerate that via lenient register - // concatenation, but stricter hardware transpilers below reject it: - // - IQM (Garnet etc.): returns only the first register -> 1-bit results - // - Rigetti: collapses all registers onto b[0] -> "bit already in use" - // - IonQ-via-Braket: similar strict behavior - // Normalizing to a single register is the canonical QASM 2 form and is - // accepted uniformly by every qBraid-reachable backend. + /// @brief Merges multiple single-bit classical registers emitted by nvq++'s + /// QASM 2 codegen into a single multi-bit `creg c[N]`. std::string normalizeClassicalRegisters(const std::string &qasm) const { + // Required to unblock qBraid-routed hardware backends. nvq++ emits one + // `creg varK[1];` per measurement. AWS Braket's classical simulators + // (SV1, DM1, TN1) tolerate that via lenient register concatenation, but + // stricter hardware transpilers below reject it: + // - IQM (Garnet etc.): returns only the first register -> 1-bit results + // - Rigetti: collapses all registers onto b[0] -> "bit already in use" + // - IonQ-via-Braket: similar strict behavior + // Normalizing to a single register is the canonical QASM 2 form and is + // accepted uniformly by every qBraid-reachable backend. static const std::regex cregDeclRx(R"(creg\s+(\w+)\s*\[\s*(\d+)\s*\]\s*;)"); std::vector> cregs; @@ -317,6 +338,7 @@ class QbraidServerHelper : public ServerHelper { return out; } + /// @brief Returns the headers for the server requests. RestHeaders getHeaders() override { if (backendConfig.find("api_key") == backendConfig.end()) { throw std::runtime_error( @@ -330,6 +352,7 @@ class QbraidServerHelper : public ServerHelper { return headers; } + /// @brief Helper method to retrieve the value of an environment variable. std::string getEnvVar(const std::string &key, const std::string &defaultVal, const bool isRequired) const { const char *env_var = std::getenv(key.c_str()); @@ -343,6 +366,8 @@ class QbraidServerHelper : public ServerHelper { return std::string(env_var); } + /// @brief Helper function to get a value from config or return a default + /// value. std::string getValueOrDefault(const BackendConfig &config, const std::string &key, const std::string &defaultValue) const { @@ -351,4 +376,6 @@ class QbraidServerHelper : public ServerHelper { }; } // namespace cudaq +// Register the QbraidServerHelper with the name "qbraid" in the ServerHelper +// factory CUDAQ_REGISTER_TYPE(cudaq::ServerHelper, cudaq::QbraidServerHelper, qbraid) diff --git a/targettests/execution/bug_qubit.cpp b/targettests/execution/bug_qubit.cpp index 6b33d51778c..d3b3d01d59e 100644 --- a/targettests/execution/bug_qubit.cpp +++ b/targettests/execution/bug_qubit.cpp @@ -17,6 +17,7 @@ // RUN: IQM_QPU_QA=%iqm_tests_dir/Crystal_20.txt %t // RUN: IQM_QPU_QA=%iqm_tests_dir/Crystal_54.txt %t // RUN: nvq++ --target oqc --emulate %s -o %t && %t +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t; fi diff --git a/targettests/execution/callable_kernel_arg.cpp b/targettests/execution/callable_kernel_arg.cpp index 7eeca0e5bbc..a036b046c5a 100644 --- a/targettests/execution/callable_kernel_arg.cpp +++ b/targettests/execution/callable_kernel_arg.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_5.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi diff --git a/targettests/execution/cudaq_observe.cpp b/targettests/execution/cudaq_observe.cpp index a28f7537f2e..230775628f9 100644 --- a/targettests/execution/cudaq_observe.cpp +++ b/targettests/execution/cudaq_observe.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_5.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi diff --git a/targettests/execution/if_jit.cpp b/targettests/execution/if_jit.cpp index 7f3eb72205d..9bc39c2e3be 100644 --- a/targettests/execution/if_jit.cpp +++ b/targettests/execution/if_jit.cpp @@ -14,6 +14,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_5.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi diff --git a/targettests/execution/int8_t.cpp b/targettests/execution/int8_t.cpp index 8323b5f0acb..d38bfd799d7 100644 --- a/targettests/execution/int8_t.cpp +++ b/targettests/execution/int8_t.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_5.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi diff --git a/targettests/execution/int8_t_free_func.cpp b/targettests/execution/int8_t_free_func.cpp index 0cf8f4bd156..8a7642813aa 100644 --- a/targettests/execution/int8_t_free_func.cpp +++ b/targettests/execution/int8_t_free_func.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_5.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi diff --git a/targettests/execution/load_value.cpp b/targettests/execution/load_value.cpp index 1d1412980b7..46513671e84 100644 --- a/targettests/execution/load_value.cpp +++ b/targettests/execution/load_value.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_5.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi diff --git a/targettests/execution/sudoku_2x2-1.cpp b/targettests/execution/sudoku_2x2-1.cpp index 0ee64a18855..df05df508fa 100644 --- a/targettests/execution/sudoku_2x2-1.cpp +++ b/targettests/execution/sudoku_2x2-1.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_20.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on diff --git a/targettests/execution/sudoku_2x2-bit_name.cpp b/targettests/execution/sudoku_2x2-bit_name.cpp index 809e237dda3..5ecff676380 100644 --- a/targettests/execution/sudoku_2x2-bit_name.cpp +++ b/targettests/execution/sudoku_2x2-bit_name.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_20.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on diff --git a/targettests/execution/sudoku_2x2-reg_name.cpp b/targettests/execution/sudoku_2x2-reg_name.cpp index a75e6f04d0e..6fc79267b65 100644 --- a/targettests/execution/sudoku_2x2-reg_name.cpp +++ b/targettests/execution/sudoku_2x2-reg_name.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_20.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on diff --git a/targettests/execution/sudoku_2x2.cpp b/targettests/execution/sudoku_2x2.cpp index ff3906f2595..b86eddcbead 100644 --- a/targettests/execution/sudoku_2x2.cpp +++ b/targettests/execution/sudoku_2x2.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_20.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on diff --git a/targettests/execution/swap_gate.cpp b/targettests/execution/swap_gate.cpp index e9d8092dd56..e836b58f99a 100644 --- a/targettests/execution/swap_gate.cpp +++ b/targettests/execution/swap_gate.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_5.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi diff --git a/targettests/execution/variable_size_qreg.cpp b/targettests/execution/variable_size_qreg.cpp index 9844855ffc8..9d6f35f0adc 100644 --- a/targettests/execution/variable_size_qreg.cpp +++ b/targettests/execution/variable_size_qreg.cpp @@ -12,6 +12,7 @@ // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --emulate %s -o %t && IQM_QPU_QA=%iqm_tests_dir/Crystal_5.txt %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: if %qci_avail; then nvq++ --target qci --emulate %s -o %t && %t | FileCheck %s; fi diff --git a/targettests/qbraid/bug_qubit.cpp b/targettests/qbraid/bug_qubit.cpp index 2f53f71dd06..05533521413 100644 --- a/targettests/qbraid/bug_qubit.cpp +++ b/targettests/qbraid/bug_qubit.cpp @@ -6,45 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// This code is from Issue 251. - -// clang-format off -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t -// RUN: cudaq-quake %cpp_std %s | cudaq-opt --promote-qubit-allocation | FileCheck --check-prefixes=MLIR %s - -#include -#include - -struct simple_x { - void operator()() __qpu__ { - cudaq::qubit q; - x(q); - mz(q); - } -}; - -// MLIR-LABEL: func.func @__nvqpp__mlirgen__simple_x() -// MLIR-NOT: quake.alloca !quake.ref -// MLIR: %[[VAL_0:.*]] = quake.alloca !quake.veq<1> -// MLIR-NEXT: %[[VAL_1:.*]] = quake.extract_ref %[[VAL_0]][0] : (!quake.veq<1>) -> !quake.ref - -int main() { - auto result = cudaq::sample(simple_x{}); - -#ifndef SYNTAX_CHECK - std::cout << result.most_probable() << '\n'; - assert("1" == result.most_probable()); -#endif - - return 0; -} - -// CHECK: 1 +// RUN: echo skipping +#include "../execution/bug_qubit.cpp" diff --git a/targettests/qbraid/callable_kernel_arg.cpp b/targettests/qbraid/callable_kernel_arg.cpp index afa5fd8d960..7a6ca74ee20 100644 --- a/targettests/qbraid/callable_kernel_arg.cpp +++ b/targettests/qbraid/callable_kernel_arg.cpp @@ -6,45 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// clang-format off -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t -// clang-format on - -#include -#include - -__qpu__ void bar(cudaq::qubit &q) { x(q); } - -struct baz { - __qpu__ void operator()(cudaq::qubit &q) { x(q); } -}; - -struct foo { - template - __qpu__ void operator()(CallableKernel &&func, int size) { - cudaq::qvector q(size); - func(q[0]); - auto result = mz(q[0]); - } -}; - -int main() { - auto result = cudaq::sample(1000, foo{}, baz{}, /*qreg size*/ 1); - -#ifndef SYNTAX_CHECK - std::cout << result.most_probable() << '\n'; - assert("1" == result.most_probable()); -#endif - - return 0; -} - -// CHECK: 1 +// RUN: echo skipping +#include "../execution/callable_kernel_arg.cpp" diff --git a/targettests/qbraid/cudaq_observe.cpp b/targettests/qbraid/cudaq_observe.cpp index 0415480c32e..1b75a817e14 100644 --- a/targettests/qbraid/cudaq_observe.cpp +++ b/targettests/qbraid/cudaq_observe.cpp @@ -6,52 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// REQUIRES: c++20 -// clang-format off -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s -// 2 different IQM machines for 2 different topologies -// RUN: nvq++ --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// clang-format on - -#include -#include - -// The example here shows a simple use case for the `cudaq::observe` -// function in computing expected values of provided spin_ops. - -struct ansatz { - auto operator()(double theta) __qpu__ { - cudaq::qvector q(2); - x(q[0]); - ry(theta, q[1]); - x(q[1], q[0]); - } -}; - -int main() { - - // Build up your spin op algebraically - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - - // Make repeatable for shots-based emulation - cudaq::set_random_seed(13); - - // Observe takes the kernel, the spin_op, and the concrete - // parameters for the kernel - double energy = cudaq::observe(ansatz{}, h, .59); - printf("Energy is %.16lf\n", energy); - return 0; -} - -// Note: seeds 2 and 12 will push this to -2 instead of -1. All other seeds in -// 1-100 range will be -1.x. - -// CHECK: Energy is -1. +// RUN: echo skipping +#include "../execution/cudaq_observe.cpp" diff --git a/targettests/qbraid/if_jit.cpp b/targettests/qbraid/if_jit.cpp index 2b763a955ff..3e916bb1e88 100644 --- a/targettests/qbraid/if_jit.cpp +++ b/targettests/qbraid/if_jit.cpp @@ -6,40 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// This code is from Issue 296. - -// clang-format off -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t -// clang-format on - -#include -#include - -__qpu__ void foo(bool value) { - cudaq::qubit q; - if (value) - x(q); - - mz(q); -} - -int main() { - auto result = cudaq::sample(100, foo, true); - -#ifndef SYNTAX_CHECK - std::cout << result.most_probable() << '\n'; - assert("1" == result.most_probable()); -#endif - - return 0; -} - -// CHECK: 1 +// RUN: echo skipping +#include "../execution/if_jit.cpp" diff --git a/targettests/qbraid/int8_t.cpp b/targettests/qbraid/int8_t.cpp new file mode 100644 index 00000000000..2c6751705ec --- /dev/null +++ b/targettests/qbraid/int8_t.cpp @@ -0,0 +1,10 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// RUN: echo skipping +#include "../execution/int8_t.cpp" diff --git a/targettests/qbraid/int8_t_free_func.cpp b/targettests/qbraid/int8_t_free_func.cpp new file mode 100644 index 00000000000..7a29487abbb --- /dev/null +++ b/targettests/qbraid/int8_t_free_func.cpp @@ -0,0 +1,10 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// RUN: echo skipping +#include "../execution/int8_t_free_func.cpp" diff --git a/targettests/qbraid/load_value.cpp b/targettests/qbraid/load_value.cpp index 6ed611e0685..e1aee9db9b5 100644 --- a/targettests/qbraid/load_value.cpp +++ b/targettests/qbraid/load_value.cpp @@ -6,58 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// clang-format off -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t -// clang-format on - -#include -#include - -__qpu__ void load_value(unsigned value) { - cudaq::qvector qubits(4); - for (std::size_t i = 0; i < 4; ++i) { - // Doesn't work, even with: `if (value)` - if (value & (1 << i)) - x(qubits[3 - i]); - } - - mz(qubits); -} - -int main() { - for (auto i = 0; i < 16; ++i) { - auto result = cudaq::sample(1000, load_value, i); - -#ifndef SYNTAX_CHECK - std::cout << result.most_probable() << '\n'; - assert(i == std::stoi(result.most_probable(), nullptr, 2)); -#endif - } - return 0; -} - -// CHECK: 0000 -// CHECK-NEXT: 0001 -// CHECK-NEXT: 0010 -// CHECK-NEXT: 0011 -// CHECK-NEXT: 0100 -// CHECK-NEXT: 0101 -// CHECK-NEXT: 0110 -// CHECK-NEXT: 0111 -// CHECK-NEXT: 1000 -// CHECK-NEXT: 1001 -// CHECK-NEXT: 1010 -// CHECK-NEXT: 1011 -// CHECK-NEXT: 1100 -// CHECK-NEXT: 1101 -// CHECK-NEXT: 1110 -// CHECK-NEXT: 1111 +// RUN: echo skipping +#include "../execution/load_value.cpp" diff --git a/targettests/qbraid/sudoku_2x2-1.cpp b/targettests/qbraid/sudoku_2x2-1.cpp index 952d93495f6..3fae8d26e6c 100644 --- a/targettests/qbraid/sudoku_2x2-1.cpp +++ b/targettests/qbraid/sudoku_2x2-1.cpp @@ -6,74 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// REQUIRES: c++20 -// clang-format off -// RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// clang-format on - -#include -#include -#include -#include - -__qpu__ void reflect_uniform(cudaq::qvector<> &qubits) { - h(qubits); - x(qubits); - z(qubits[0], qubits[1], qubits[2], qubits[3]); - x(qubits); - h(qubits); -} - -__qpu__ void oracle(cudaq::qvector<> &cs, cudaq::qubit &target) { - x(cs[0], !cs[1], !cs[2], cs[3], target); - x(!cs[0], cs[1], cs[2], !cs[3], target); -} - -__qpu__ void grover() { - cudaq::qvector qubits(4); - cudaq::qubit ancilla; - - // Initialization - x(ancilla); - h(ancilla); - h(qubits); // uniform initialization - - // Don't work?: - for (int i = 0; i < 2; ++i) { - oracle(qubits, ancilla); - reflect_uniform(qubits); - } - - mz(qubits); -}; - -int main() { - auto result = cudaq::sample(1000, grover); - -#ifndef SYNTAX_CHECK - std::vector strings; - for (auto &&[bits, count] : result) { - strings.push_back(bits); - } - std::sort(strings.begin(), strings.end(), [&](auto &a, auto &b) { - return result.count(a) > result.count(b); - }); - std::cout << strings[0] << '\n'; - std::cout << strings[1] << '\n'; - - std::unordered_set most_probable{strings[0], strings[1]}; - assert(most_probable.count("1001") == 1); - assert(most_probable.count("0110") == 1); -#endif - - return 0; -} - -// CHECK-DAG: 1001 -// CHECK-DAG: 0110 +// RUN: echo skipping +#include "../execution/sudoku_2x2-1.cpp" diff --git a/targettests/qbraid/sudoku_2x2-bit_name.cpp b/targettests/qbraid/sudoku_2x2-bit_name.cpp new file mode 100644 index 00000000000..f875955b7be --- /dev/null +++ b/targettests/qbraid/sudoku_2x2-bit_name.cpp @@ -0,0 +1,10 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// RUN: echo skipping +#include "../execution/sudoku_2x2-bit_name.cpp" diff --git a/targettests/qbraid/sudoku_2x2-bit_names.cpp b/targettests/qbraid/sudoku_2x2-bit_names.cpp deleted file mode 100644 index e0de3cdd4da..00000000000 --- a/targettests/qbraid/sudoku_2x2-bit_names.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -// REQUIRES: c++20 -// clang-format off -// RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// clang-format on - -#include -#include -#include -#include - -__qpu__ void reflect_uniform(cudaq::qvector<> &qubits) { - h(qubits); - x(qubits); - z(qubits[0], qubits[1], qubits[2], qubits[3]); - x(qubits); - h(qubits); -} - -__qpu__ void oracle(cudaq::qvector<> &cs, cudaq::qubit &target) { - x(cs[0], !cs[1], !cs[2], cs[3], target); - x(!cs[0], cs[1], cs[2], !cs[3], target); -} - -__qpu__ void grover() { - cudaq::qvector qubits(4); - cudaq::qubit ancilla; - - // Initialization - x(ancilla); - h(ancilla); - h(qubits); // uniform initialization - - oracle(qubits, ancilla); - reflect_uniform(qubits); - oracle(qubits, ancilla); - reflect_uniform(qubits); - - auto groverQubits0 = mz(qubits[0]); - auto groverQubits1 = mz(qubits[1]); - auto groverQubits2 = mz(qubits[2]); - auto groverQubits3 = mz(qubits[3]); -}; - -int main() { - auto result = cudaq::sample(1000, grover); - result.dump(); - - auto& platform = cudaq::get_platform(); - if (platform.is_remote() || platform.is_emulated()) { - // Make sure that the get_marginal() results for the individual register names - // match the subset of the bits from the global register. - // Note that this will fail if you only compile this in library mode. - auto numBits = result.begin()->first.size(); - std::cout << "Checking " << numBits << " bits against global register\n"; - for (size_t b = 0; b < numBits; b++) { - auto regName = "groverQubits" + std::to_string(b); - auto valFromRegName = result.get_marginal({0}, regName); - auto valFromGlobal = result.get_marginal({b}); - if (valFromRegName.to_map() != valFromGlobal.to_map()) { - std::cout << "--- MISMATCH DETECTED in bit " << b << " ---\n"; - valFromRegName.dump(); - valFromGlobal.dump(); - // Mark test failure - assert(valFromRegName.to_map() == valFromGlobal.to_map()); - } - } - } - -#ifndef SYNTAX_CHECK - std::vector strings; - for (auto &&[bits, count] : result) { - strings.push_back(bits); - } - std::sort(strings.begin(), strings.end(), [&](auto& a, auto& b) { - return result.count(a) > result.count(b); - }); - std::cout << strings[0] << '\n'; - std::cout << strings[1] << '\n'; - - std::unordered_set most_probable{strings[0], strings[1]}; - assert(most_probable.count("1001") == 1); - assert(most_probable.count("0110") == 1); -#endif - - return 0; -} - -// CHECK-DAG: 1001 -// CHECK-DAG: 0110 diff --git a/targettests/qbraid/sudoku_2x2-reg_name.cpp b/targettests/qbraid/sudoku_2x2-reg_name.cpp index eb42663a5bc..17a48caec48 100644 --- a/targettests/qbraid/sudoku_2x2-reg_name.cpp +++ b/targettests/qbraid/sudoku_2x2-reg_name.cpp @@ -6,74 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// REQUIRES: c++20 -// clang-format off -// RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// clang-format on - -#include -#include -#include -#include - -__qpu__ void reflect_uniform(cudaq::qvector<> &qubits) { - h(qubits); - x(qubits); - z(qubits[0], qubits[1], qubits[2], qubits[3]); - x(qubits); - h(qubits); -} - -__qpu__ void oracle(cudaq::qvector<> &cs, cudaq::qubit &target) { - x(cs[0], !cs[1], !cs[2], cs[3], target); - x(!cs[0], cs[1], cs[2], !cs[3], target); -} - -__qpu__ void grover() { - cudaq::qvector qubits(4); - cudaq::qubit ancilla; - - // Initialization - x(ancilla); - h(ancilla); - h(qubits); // uniform initialization - - oracle(qubits, ancilla); - reflect_uniform(qubits); - oracle(qubits, ancilla); - reflect_uniform(qubits); - - auto groverQubits = mz(qubits); -}; - -int main() { - auto result = cudaq::sample(1000, grover); - result.dump(); - -#ifndef SYNTAX_CHECK - std::vector strings; - for (auto &&[bits, count] : result) { - strings.push_back(bits); - } - std::sort(strings.begin(), strings.end(), [&](auto& a, auto& b) { - return result.count(a) > result.count(b); - }); - std::cout << strings[0] << '\n'; - std::cout << strings[1] << '\n'; - - std::unordered_set most_probable{strings[0], strings[1]}; - assert(most_probable.count("1001") == 1); - assert(most_probable.count("0110") == 1); -#endif - - return 0; -} - -// CHECK-DAG: 1001 -// CHECK-DAG: 0110 +// RUN: echo skipping +#include "../execution/sudoku_2x2-reg_name.cpp" diff --git a/targettests/qbraid/sudoku_2x2.cpp b/targettests/qbraid/sudoku_2x2.cpp index a5cb54c0843..090b230072a 100644 --- a/targettests/qbraid/sudoku_2x2.cpp +++ b/targettests/qbraid/sudoku_2x2.cpp @@ -6,73 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// REQUIRES: c++20 -// clang-format off -// RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// clang-format on - -#include -#include -#include -#include - -__qpu__ void reflect_uniform(cudaq::qvector<> &qubits) { - h(qubits); - x(qubits); - z(qubits[0], qubits[1], qubits[2], qubits[3]); - x(qubits); - h(qubits); -} - -__qpu__ void oracle(cudaq::qvector<> &cs, cudaq::qubit &target) { - x(cs[0], !cs[1], !cs[2], cs[3], target); - x(!cs[0], cs[1], cs[2], !cs[3], target); -} - -__qpu__ void grover() { - cudaq::qvector qubits(4); - cudaq::qubit ancilla; - - // Initialization - x(ancilla); - h(ancilla); - h(qubits); // uniform initialization - - oracle(qubits, ancilla); - reflect_uniform(qubits); - oracle(qubits, ancilla); - reflect_uniform(qubits); - - mz(qubits); -}; - -int main() { - auto result = cudaq::sample(1000, grover); - -#ifndef SYNTAX_CHECK - std::vector strings; - for (auto &&[bits, count] : result) { - strings.push_back(bits); - } - std::sort(strings.begin(), strings.end(), [&](auto& a, auto& b) { - return result.count(a) > result.count(b); - }); - std::cout << strings[0] << '\n'; - std::cout << strings[1] << '\n'; - - std::unordered_set most_probable{strings[0], strings[1]}; - assert(most_probable.count("1001") == 1); - assert(most_probable.count("0110") == 1); -#endif - - return 0; -} - -// CHECK-DAG: 1001 -// CHECK-DAG: 0110 +// RUN: echo skipping +#include "../execution/sudoku_2x2.cpp" diff --git a/targettests/qbraid/swap_gate.cpp b/targettests/qbraid/swap_gate.cpp index 7331ecbd262..c592ce69b31 100644 --- a/targettests/qbraid/swap_gate.cpp +++ b/targettests/qbraid/swap_gate.cpp @@ -6,38 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// clang-format off -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t && %t | FileCheck %s - -#include "cudaq.h" -#include - -int main() { - - auto swapKernel = []() __qpu__ { - cudaq::qvector q(2); - x(q[0]); - swap(q[0], q[1]); - - mz(q); - }; - - auto counts = cudaq::sample(swapKernel); - -#ifndef SYNTAX_CHECK - std::cout << counts.most_probable() << '\n'; - assert("01" == counts.most_probable()); -#endif - - return 0; -} - -// CHECK: 01 +// RUN: echo skipping +#include "../execution/swap_gate.cpp" diff --git a/targettests/qbraid/test-int8_t.cpp b/targettests/qbraid/test-int8_t.cpp deleted file mode 100644 index 7f3cf8b63c3..00000000000 --- a/targettests/qbraid/test-int8_t.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -// clang-format off -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t -// clang-format on - -#include -#include - -struct variable_qreg { - __qpu__ void operator()(std::uint8_t value) { - cudaq::qvector qubits(value); - - mz(qubits); - } -}; - -int main() { - for (auto i = 1; i < 5; ++i) { - auto result = cudaq::sample(1000, variable_qreg{}, i); - -#ifndef SYNTAX_CHECK - std::cout << result.most_probable() << '\n'; - assert(std::string(i, '0') == result.most_probable()); -#endif - } - - return 0; -} - -// CHECK: 0 -// CHECK: 00 -// CHECK: 000 -// CHECK: 0000 diff --git a/targettests/qbraid/test-int8_t_free_func.cpp b/targettests/qbraid/test-int8_t_free_func.cpp deleted file mode 100644 index 6c3eea548d8..00000000000 --- a/targettests/qbraid/test-int8_t_free_func.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -// clang-format off -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t -// clang-format on - -#include -#include - -__qpu__ void variable_qreg(std::uint8_t value) { - cudaq::qvector qubits(value); - - mz(qubits); -} - -int main() { - for (auto i = 1; i < 5; ++i) { - auto result = cudaq::sample(1000, variable_qreg, i); - -#ifndef SYNTAX_CHECK - std::cout << result.most_probable() << '\n'; - assert(std::string(i, '0') == result.most_probable()); -#endif - } - - return 0; -} - -// CHECK: 0 -// CHECK-NEXT: 00 -// CHECK-NEXT: 000 -// CHECK-NEXT: 0000 diff --git a/targettests/qbraid/variable_size_qreg.cpp b/targettests/qbraid/variable_size_qreg.cpp index 120fcede939..cc4845f4df9 100644 --- a/targettests/qbraid/variable_size_qreg.cpp +++ b/targettests/qbraid/variable_size_qreg.cpp @@ -6,41 +6,5 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// clang-format off -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target qbraid --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi -// RUN: nvq++ -std=c++17 --enable-mlir %s -o %t -// clang-format on - -#include -#include - -__qpu__ void variable_qreg(unsigned value) { - cudaq::qvector qubits(value); - - mz(qubits); -} - -int main() { - for (auto i = 1; i < 5; ++i) { - auto result = cudaq::sample(1000, variable_qreg, i); - -#ifndef SYNTAX_CHECK - std::cout << result.most_probable() << '\n'; - assert(std::string(i, '0') == result.most_probable()); -#endif - } - - return 0; -} - -// CHECK: 0 -// CHECK-NEXT: 00 -// CHECK-NEXT: 000 -// CHECK-NEXT: 0000 +// RUN: echo skipping +#include "../execution/variable_size_qreg.cpp" From 7ab08ec4343c00165e6d8d0f84877c6e3396faeb Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Wed, 22 Apr 2026 05:29:50 +0000 Subject: [PATCH 14/18] fix: EADDRINUSE error in CI due to port collision with HorizonServerHelper port Signed-off-by: TheGupta2012 --- unittests/backends/qbraid/CMakeLists.txt | 2 +- unittests/backends/qbraid/QbraidStartServerAndTest.sh.in | 2 +- unittests/backends/qbraid/QbraidTester.cpp | 8 ++++---- utils/mock_qpu/qbraid/__init__.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/unittests/backends/qbraid/CMakeLists.txt b/unittests/backends/qbraid/CMakeLists.txt index e088f6546de..e1e7a1c07e7 100644 --- a/unittests/backends/qbraid/CMakeLists.txt +++ b/unittests/backends/qbraid/CMakeLists.txt @@ -9,7 +9,7 @@ add_backend_unittest_executable(test_qbraid SOURCES QbraidTester.cpp BACKEND qbraid - BACKEND_CONFIG "qbraid emulate=false url=http://localhost:62452 api_key=00000000000000000000000000000000" + BACKEND_CONFIG "qbraid emulate=false url=http://localhost:62454 api_key=00000000000000000000000000000000" ) configure_file("QbraidStartServerAndTest.sh.in" "${CMAKE_BINARY_DIR}/unittests/backends/qbraid/QbraidStartServerAndTest.sh" @ONLY) diff --git a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in index 3510a3077f1..4221dc109fa 100644 --- a/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in +++ b/unittests/backends/qbraid/QbraidStartServerAndTest.sh.in @@ -13,7 +13,7 @@ checkServerConnection() { import socket try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(("localhost", 62452)) + s.connect(("localhost", 62454)) s.close() except Exception: exit(1) diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp index 37e99325139..bf5d4128fe2 100644 --- a/unittests/backends/qbraid/QbraidTester.cpp +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -15,7 +15,7 @@ #include // Update the backend string to match the QBraid format -std::string mockPort = "62452"; +std::string mockPort = "62454"; std::string backendStringTemplate = "qbraid;emulate;false;url;http://localhost:{}"; @@ -161,7 +161,7 @@ CUDAQ_TEST(QbraidTester, checkJobFailure) { cudaq::RestClient client; nlohmann::json body = nlohmann::json::object(); std::map headers; - auto armed = client.post("http://localhost:62452/", "test/fail_next", body, + auto armed = client.post("http://localhost:62454/", "test/fail_next", body, headers, /*enableLogging=*/false); ASSERT_TRUE(armed.value("armed", false)); @@ -180,7 +180,7 @@ CUDAQ_TEST(QbraidTester, checkResultRetry) { nlohmann::json body = nlohmann::json::object(); std::map headers; auto armed = - client.post("http://localhost:62452/", "test/delay_next_results/2", body, + client.post("http://localhost:62454/", "test/delay_next_results/2", body, headers, /*enableLogging=*/false); ASSERT_EQ(armed.value("remaining", -1), 2); @@ -200,7 +200,7 @@ CUDAQ_TEST(QbraidTester, checkResultRetryExhaustion) { nlohmann::json body = nlohmann::json::object(); std::map headers; auto armed = - client.post("http://localhost:62452/", "test/delay_next_results/10", body, + client.post("http://localhost:62454/", "test/delay_next_results/10", body, headers, /*enableLogging=*/false); ASSERT_EQ(armed.value("remaining", -1), 10); diff --git a/utils/mock_qpu/qbraid/__init__.py b/utils/mock_qpu/qbraid/__init__.py index 20a559fff7c..b6f33926af0 100644 --- a/utils/mock_qpu/qbraid/__init__.py +++ b/utils/mock_qpu/qbraid/__init__.py @@ -315,4 +315,4 @@ def startServer(port): if __name__ == "__main__": - startServer(62452) + startServer(62454) From e43b46d1f716475263eec6e563e33e7c720534dd Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Thu, 23 Apr 2026 07:22:47 +0000 Subject: [PATCH 15/18] add: error handling for status codes in result fetch and corresponding tests Signed-off-by: TheGupta2012 --- python/tests/backends/test_qbraid.py | 59 +++++++++++++- .../helpers/qbraid/QbraidServerHelper.cpp | 32 +++++++- unittests/backends/qbraid/QbraidTester.cpp | 76 +++++++++++++++++++ utils/mock_qpu/qbraid/__init__.py | 29 +++++++ 4 files changed, 194 insertions(+), 2 deletions(-) diff --git a/python/tests/backends/test_qbraid.py b/python/tests/backends/test_qbraid.py index c42cc3e1a02..8aa9b0dff57 100644 --- a/python/tests/backends/test_qbraid.py +++ b/python/tests/backends/test_qbraid.py @@ -8,6 +8,7 @@ import os from multiprocessing import Process +from urllib.request import Request, urlopen import cudaq import pytest @@ -20,7 +21,7 @@ print("Mock qpu not available, skipping qBraid tests.") pytest.skip("Mock qpu not available.", allow_module_level=True) -port = 62452 +port = 62454 # Default machine for tests. Mirrors the real qBraid device string format. TEST_MACHINE = "qbraid:qbraid:sim:qir-sv" @@ -177,6 +178,62 @@ def test_qbraid_machine_alternative_device(): assert len(counts) >= 1 +def _arm_result_status(code: int): + """Force the next /result call on the mock to return the given HTTP code. + + Resets prior test-hook state first so the test is order-independent. + """ + reset_url = f"http://localhost:{port}/test/reset" + arm_url = f"http://localhost:{port}/test/force_next_result_status/{code}" + # POST with empty body; no response parsing needed. + urlopen(Request(reset_url, data=b"", method="POST"), timeout=5).read() + urlopen(Request(arm_url, data=b"", method="POST"), timeout=5).read() + + +def test_qbraid_result_auth_failure(): + """401 on /result -> terminal auth error; message names the status.""" + _arm_result_status(401) + kernel = cudaq.make_kernel() + qubit = kernel.qalloc() + kernel.h(qubit) + kernel.mz(qubit) + with pytest.raises(RuntimeError, match="authentication failed"): + cudaq.sample(kernel) + + +def test_qbraid_result_forbidden(): + """403 on /result -> same terminal auth translation as 401.""" + _arm_result_status(403) + kernel = cudaq.make_kernel() + qubit = kernel.qalloc() + kernel.h(qubit) + kernel.mz(qubit) + with pytest.raises(RuntimeError, match="authentication failed"): + cudaq.sample(kernel) + + +def test_qbraid_result_not_found(): + """404 on /result -> terminal 'result not found' error.""" + _arm_result_status(404) + kernel = cudaq.make_kernel() + qubit = kernel.qalloc() + kernel.h(qubit) + kernel.mz(qubit) + with pytest.raises(RuntimeError, match="result not found"): + cudaq.sample(kernel) + + +def test_qbraid_result_server_error_retries(): + """500 on /result is retryable; hook clears after one call so retry wins.""" + _arm_result_status(500) + kernel = cudaq.make_kernel() + qubit = kernel.qalloc() + kernel.h(qubit) + kernel.mz(qubit) + counts = cudaq.sample(kernel) + assert len(counts) >= 1 + + # leave for gdb debugging if __name__ == "__main__": loc = os.path.abspath(__file__) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index e9ec8b46e5f..d1e005ba6ef 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -252,7 +252,37 @@ class QbraidServerHelper : public ServerHelper { } } catch (const std::exception &e) { - cudaq::info("Exception when fetching results: {}", e.what()); + // RestClient throws std::runtime_error on any non-success HTTP status + // (see runtime/common/RestClient.cpp) with a fixed message format: + // "HTTP Error - status code : : " + // The code isn't exposed as a structured attribute, so we parse it + // out to distinguish terminal client errors (401/403/404) from + // transient server/network errors (5xx, parse errors) that retry. + static const std::regex statusRx(R"(status code (\d+))"); + const std::string what = e.what(); + std::smatch match; + int statusCode = 0; + if (std::regex_search(what, match, statusRx)) + statusCode = std::stoi(match[1]); + + // Terminal: auth failures - retrying will not recover. + if (statusCode == 401 || statusCode == 403) + throw std::runtime_error( + "qBraid authentication failed (HTTP " + + std::to_string(statusCode) + + "). Verify QBRAID_API_KEY or api_key target argument."); + + // Terminal: result resource genuinely does not exist. This is + // distinct from the "not yet available" race which returns + // 200 + success=false (handled above). + if (statusCode == 404) + throw std::runtime_error( + "qBraid result not found (HTTP 404) for job " + jobId + + ". The job may have been deleted or never produced results."); + + // Retryable: 5xx, network errors, JSON parse failures, etc. + cudaq::info("Exception when fetching results (attempt {}/{}): {}", + attempt + 1, maxRetries, what); if (attempt < maxRetries - 1) { int sleepTime = (attempt == 0) ? waitTime diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp index bf5d4128fe2..ff59dbdd728 100644 --- a/unittests/backends/qbraid/QbraidTester.cpp +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -212,6 +212,82 @@ CUDAQ_TEST(QbraidTester, checkResultRetryExhaustion) { EXPECT_ANY_THROW({ (void)cudaq::sample(kernel); }); } +// Helper: arm the mock to return a specific HTTP status on the next /result. +// Resets prior test-hook state first so the test is order-independent. +static void armResultStatus(int code) { + cudaq::RestClient client; + nlohmann::json body = nlohmann::json::object(); + std::map headers; + (void)client.post("http://localhost:62454/", "test/reset", body, headers, + /*enableLogging=*/false); + auto armed = + client.post("http://localhost:62454/", + "test/force_next_result_status/" + std::to_string(code), body, + headers, /*enableLogging=*/false); + ASSERT_EQ(armed.value("armed_status", -1), code); +} + +// Helper: match a substring in the exception message. +static ::testing::AssertionResult throwsWithMessage(std::function fn, + const std::string &needle) { + try { + fn(); + } catch (const std::exception &e) { + std::string what = e.what(); + if (what.find(needle) != std::string::npos) + return ::testing::AssertionSuccess(); + return ::testing::AssertionFailure() + << "exception message did not contain '" << needle << "'. Actual: '" + << what << "'"; + } + return ::testing::AssertionFailure() << "expected exception, none thrown"; +} + +// 401 on /result -> terminal auth failure, message must name the status. +CUDAQ_TEST(QbraidTester, checkResultAuthFailure) { + armResultStatus(401); + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + EXPECT_TRUE(throwsWithMessage([&]() { (void)cudaq::sample(kernel); }, + "authentication failed")); +} + +// 403 on /result -> same terminal auth failure translation as 401. +CUDAQ_TEST(QbraidTester, checkResultForbidden) { + armResultStatus(403); + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + EXPECT_TRUE(throwsWithMessage([&]() { (void)cudaq::sample(kernel); }, + "authentication failed")); +} + +// 404 on /result -> terminal "not found", message must mention the job id. +CUDAQ_TEST(QbraidTester, checkResultNotFound) { + armResultStatus(404); + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + EXPECT_TRUE(throwsWithMessage([&]() { (void)cudaq::sample(kernel); }, + "result not found")); +} + +// 500 on /result -> retryable. Force hook fires once then clears, so the +// second attempt succeeds. Sampling must not throw. +CUDAQ_TEST(QbraidTester, checkResultServerErrorRetries) { + armResultStatus(500); + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + auto counts = cudaq::sample(kernel); + EXPECT_GE(counts.size(), 1u); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); diff --git a/utils/mock_qpu/qbraid/__init__.py b/utils/mock_qpu/qbraid/__init__.py index b6f33926af0..066650fac86 100644 --- a/utils/mock_qpu/qbraid/__init__.py +++ b/utils/mock_qpu/qbraid/__init__.py @@ -45,6 +45,10 @@ class Job(BaseModel): # success=false (simulating the qbraid v2 race where status=COMPLETED before # results are queryable). Decrements on each /result call until 0. DELAY_RESULTS_COUNT = {"remaining": 0} +# Testing hook: when set, the next GET /jobs/{id}/result call raises the given +# HTTP status. Consumed (reset to None) after one call. Used to exercise the +# helper's 401/403/404/5xx handling paths. +FORCE_NEXT_RESULT_STATUS = {"code": None} def count_qubits(qasm: str) -> int: @@ -208,6 +212,23 @@ async def armDelayResults(count: int = Path(...)): return {"remaining": count} +# Test-only: force the next GET /result call to return the given HTTP status. +# Consumed after one call. +@app.post("/test/force_next_result_status/{code}") +async def armForceResultStatus(code: int = Path(...)): + FORCE_NEXT_RESULT_STATUS["code"] = code + return {"armed_status": code} + + +# Test-only: reset all test-hook globals so tests are order-independent. +@app.post("/test/reset") +async def resetTestState(): + FAIL_NEXT_JOB["enabled"] = False + DELAY_RESULTS_COUNT["remaining"] = 0 + FORCE_NEXT_RESULT_STATUS["code"] = None + return {"reset": True} + + # v2 API: GET /jobs/{job_qrn} @app.get("/jobs/{job_id}") async def getJob( @@ -256,6 +277,14 @@ async def getJobResult( x_api_key: Optional[str] = Header(None, alias="X-API-KEY"), ): """Retrieve the results of a quantum job (v2 API).""" + # Test hook: if armed, raise the requested status. Checked first so tests + # can force 401/403 even when a valid api key is present. + if FORCE_NEXT_RESULT_STATUS["code"] is not None: + forced = FORCE_NEXT_RESULT_STATUS["code"] + FORCE_NEXT_RESULT_STATUS["code"] = None + raise HTTPException(status_code=forced, + detail=f"Forced HTTP {forced} for test") + if x_api_key is None: raise HTTPException(status_code=401, detail="API key is required") From c47f54a2dc14965f21e2b122a088168a853a1abb Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Fri, 24 Apr 2026 10:49:41 +0000 Subject: [PATCH 16/18] fix: review comments, add combine meas and other codegen passes to replace qasm normalization and fix sudoku tests Signed-off-by: TheGupta2012 --- .github/workflows/integration_tests.yml | 43 ++++++- CMakeLists.txt | 5 + lib/Optimizer/CodeGen/Passes.cpp | 13 -- .../default/rest/helpers/CMakeLists.txt | 4 +- .../helpers/qbraid/QbraidServerHelper.cpp | 115 +++++++----------- .../default/rest/helpers/qbraid/qbraid.yml | 9 +- unittests/backends/CMakeLists.txt | 4 +- unittests/backends/qbraid/QbraidTester.cpp | 13 ++ 8 files changed, 120 insertions(+), 86 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 32d938209fd..7297e2faeba 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -192,7 +192,7 @@ jobs: run: | # Determine which providers to test based on inputs and event type if [[ "${{ github.event_name }}" == "schedule" || "${{ inputs.target }}" == "nightly" ]]; then - providers='["anyon", "fermioniq", "infleqtion", "ionq", "iqm", "oqc", "orca", "pasqal", "qci", "quantinuum", "scaleway", "tii"]' + providers='["anyon", "fermioniq", "infleqtion", "ionq", "iqm", "oqc", "orca", "pasqal", "qbraid", "qci", "quantinuum", "scaleway", "tii"]' else # Just run the specified target provider providers="[\"${{ inputs.target }}\"]" @@ -262,6 +262,9 @@ jobs: pasqal) filelist="docs/sphinx/targets/cpp/pasqal.cpp docs/sphinx/targets/python/pasqal.py" ;; + qbraid) + filelist="targettests/qbraid/*.cpp docs/sphinx/targets/cpp/qbraid.cpp docs/sphinx/targets/python/qbraid.py" + ;; qci) filelist="targettests/qci/*.cpp" ;; @@ -381,6 +384,11 @@ jobs: echo "PASQAL_PROJECT_ID=${{ secrets.PASQAL_PROJECT_ID }}" >> $GITHUB_ENV echo "PASQAL_MACHINE_TARGET=EMU_FREE" >> $GITHUB_ENV ;; + qbraid) + echo "### Setting up qBraid account" >> $GITHUB_STEP_SUMMARY + echo "::add-mask::${{ secrets.QBRAID_API_KEY }}" + echo "QBRAID_API_KEY=${{ secrets.QBRAID_API_KEY }}" >> $GITHUB_ENV + ;; qci) echo "### Setting up QCI account" >> $GITHUB_STEP_SUMMARY echo "::add-mask::${{ secrets.QCI_AUTH_TOKEN }}" @@ -672,6 +680,39 @@ jobs: fi ;; + qbraid) + if [[ "$filename" == *.cpp ]]; then + nvq++ -v $filename --target qbraid --qbraid-machine qbraid:qbraid:sim:qir-sv + test_status=$? + if [ $test_status -eq 0 ]; then + ./a.out + test_status=$? + if [ $test_status -eq 0 ]; then + echo ":white_check_mark: Successfully ran test: $filename" >> $GITHUB_STEP_SUMMARY + else + echo ":x: Test failed (failed to execute): $filename" >> $GITHUB_STEP_SUMMARY + test_err_sum=$((test_err_sum+1)) + fi + else + echo ":x: Test failed (failed to compile): $filename" >> $GITHUB_STEP_SUMMARY + test_err_sum=$((test_err_sum+1)) + fi + elif [[ "$filename" == *.py ]]; then + python3 $filename 1> /dev/null + test_status=$? + if [ $test_status -eq 0 ]; then + echo ":white_check_mark: Successfully ran test: $filename" >> $GITHUB_STEP_SUMMARY + else + echo ":x: Test failed (failed to execute): $filename" >> $GITHUB_STEP_SUMMARY + test_err_sum=$((test_err_sum+1)) + fi + else + echo "::warning::Unsupported file type: $filename" + echo ":warning: Test skipped (unsupported file type): $filename" >> $GITHUB_STEP_SUMMARY + test_skip_sum=$((test_skip_sum+1)) + fi + ;; + qci) nvq++ -v $filename --target qci test_status=$? diff --git a/CMakeLists.txt b/CMakeLists.txt index 04a1c07db34..0baf807653e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,11 @@ if (NOT DEFINED CUDAQ_ENABLE_SCALEWAY_BACKEND) set(CUDAQ_ENABLE_SCALEWAY_BACKEND ON CACHE BOOL "Enable building the Scaleway target.") endif() +# Enable qBraid target by default. +if (NOT DEFINED CUDAQ_ENABLE_QBRAID_BACKEND) + set(CUDAQ_ENABLE_QBRAID_BACKEND ON CACHE BOOL "Enable building the qBraid target.") +endif() + # Generate a CompilationDatabase (compile_commands.json file) for our build, # for use by clang_complete, YouCompleteMe, etc. set(CMAKE_EXPORT_COMPILE_COMMANDS 1) diff --git a/lib/Optimizer/CodeGen/Passes.cpp b/lib/Optimizer/CodeGen/Passes.cpp index 808db9e3e2a..8ff6c53c2d1 100644 --- a/lib/Optimizer/CodeGen/Passes.cpp +++ b/lib/Optimizer/CodeGen/Passes.cpp @@ -100,16 +100,6 @@ static void addFermioniqPipeline(OpPassManager &pm) { pm.addPass(createBasisConversion(options)); } -static void addQbraidPipeline(OpPassManager &pm) { - using namespace cudaq::opt; - std::string basis[] = { - "h", "s", "t", "rx", "ry", "rz", "x", "y", "z", "x(1)", - }; - BasisConversionOptions options; - options.basis = basis; - pm.addPass(createBasisConversion(options)); -} - void cudaq::opt::registerTargetPipelines() { PassPipelineRegistration<>("anyon-cgate-set-mapping", "Convert kernels to Anyon gate set.", @@ -135,9 +125,6 @@ void cudaq::opt::registerTargetPipelines() { PassPipelineRegistration<>("fermioniq-gate-set-mapping", "Convert kernels to Fermioniq gate set.", addFermioniqPipeline); - PassPipelineRegistration<>("qbraid-gate-set-mapping", - "Convert kernels to qBraid gate set.", - addQbraidPipeline); } void cudaq::opt::registerCodeGenDialect(DialectRegistry ®istry) { diff --git a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt index 55fff380909..4574b6ba8fe 100644 --- a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt @@ -27,4 +27,6 @@ endif() if(CUDAQ_ENABLE_TII_BACKEND) add_subdirectory(tii) endif() -add_subdirectory(qbraid) +if(CUDAQ_ENABLE_QBRAID_BACKEND) + add_subdirectory(qbraid) +endif() diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index d1e005ba6ef..5996d1ffe64 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -11,7 +11,6 @@ #include "cudaq/Support/Version.h" #include "cudaq/runtime/logger/logger.h" #include "cudaq/utils/cudaq_utils.h" -#include #include #include @@ -103,7 +102,7 @@ class QbraidServerHelper : public ServerHelper { // v2 API: program is a structured object with format and data nlohmann::json program; program["format"] = "qasm2"; - program["data"] = normalizeClassicalRegisters(circuitCode.code); + program["data"] = circuitCode.code; job["program"] = program; // v2 API: name is a top-level field (not nested under tags) @@ -236,8 +235,43 @@ class QbraidServerHelper : public ServerHelper { : static_cast(count); } + // The returned bitstring spans every measured qubit, including + // compiler-generated ancillae that the user never declared. Reduce + // it down to the user-visible qubits using the output_names entry + // populated by the framework (Executor.cpp writes one per submitted + // circuit; Future.cpp re-initializes the helper with that config + // before processResults runs). Mirrors the IonQ / Braket helpers. + cudaq::ExecutionResult fullExecResults{counts}; + auto fullSampleResults = cudaq::sample_result{fullExecResults}; + std::vector execResults; - execResults.emplace_back(ExecutionResult{counts}); + + auto outputNamesIt = outputNames.find(jobId); + if (outputNamesIt != outputNames.end() && + !outputNamesIt->second.empty()) { + auto &job_output_names = outputNamesIt->second; + + std::vector qubitNumbers; + qubitNumbers.reserve(job_output_names.size()); + for (auto &[result, info] : job_output_names) + qubitNumbers.push_back(info.qubitNum); + + auto subset = fullSampleResults.get_marginal(qubitNumbers); + execResults.emplace_back(ExecutionResult{subset.to_map()}); + + // Emit one single-bit register per named result so that + // `sample_result::to_map(registerName)` still works. + for (const auto &[result, info] : job_output_names) { + CountsDictionary regCounts; + for (const auto &[bits, count] : fullSampleResults) + regCounts[std::string{bits[info.qubitNum]}] += count; + execResults.emplace_back(regCounts, info.registerName); + } + } else { + // No output_names available: fall back to the full flat counts. + execResults.emplace_back(ExecutionResult{counts}); + } + return cudaq::sample_result(execResults); } @@ -256,7 +290,7 @@ class QbraidServerHelper : public ServerHelper { // (see runtime/common/RestClient.cpp) with a fixed message format: // "HTTP Error - status code : : " // The code isn't exposed as a structured attribute, so we parse it - // out to distinguish terminal client errors (401/403/404) from + // out to distinguish terminal client errors (401/403/404/409) from // transient server/network errors (5xx, parse errors) that retry. static const std::regex statusRx(R"(status code (\d+))"); const std::string what = e.what(); @@ -280,6 +314,15 @@ class QbraidServerHelper : public ServerHelper { "qBraid result not found (HTTP 404) for job " + jobId + ". The job may have been deleted or never produced results."); + // Terminal: job reached a non-success terminal state (FAILED or + // CANCELLED). qBraid v2 returns 409 Conflict on /result in that case + // because no measurement data will ever be produced. + if (statusCode == 409) + throw std::runtime_error( + "qBraid job " + jobId + + " did not produce results (HTTP 409). The job likely FAILED " + "or was CANCELLED."); + // Retryable: 5xx, network errors, JSON parse failures, etc. cudaq::info("Exception when fetching results (attempt {}/{}): {}", attempt + 1, maxRetries, what); @@ -304,70 +347,6 @@ class QbraidServerHelper : public ServerHelper { } private: - /// @brief Merges multiple single-bit classical registers emitted by nvq++'s - /// QASM 2 codegen into a single multi-bit `creg c[N]`. - std::string normalizeClassicalRegisters(const std::string &qasm) const { - // Required to unblock qBraid-routed hardware backends. nvq++ emits one - // `creg varK[1];` per measurement. AWS Braket's classical simulators - // (SV1, DM1, TN1) tolerate that via lenient register concatenation, but - // stricter hardware transpilers below reject it: - // - IQM (Garnet etc.): returns only the first register -> 1-bit results - // - Rigetti: collapses all registers onto b[0] -> "bit already in use" - // - IonQ-via-Braket: similar strict behavior - // Normalizing to a single register is the canonical QASM 2 form and is - // accepted uniformly by every qBraid-reachable backend. - static const std::regex cregDeclRx(R"(creg\s+(\w+)\s*\[\s*(\d+)\s*\]\s*;)"); - - std::vector> cregs; - for (auto it = std::sregex_iterator(qasm.begin(), qasm.end(), cregDeclRx); - it != std::sregex_iterator(); ++it) { - cregs.emplace_back((*it)[1].str(), std::stoi((*it)[2].str())); - } - - // Nothing to do if the QASM already has a single classical register. - if (cregs.size() <= 1) - return qasm; - - std::map offsetByName; - int totalBits = 0; - for (auto &[name, size] : cregs) { - offsetByName[name] = totalBits; - totalBits += size; - } - - std::string out = qasm; - - // Rewrite every `-> NAME[i]` target BEFORE we mutate the creg declarations. - for (auto &[name, size] : cregs) { - int base = offsetByName[name]; - for (int i = 0; i < size; ++i) { - std::regex measureTargetRx("->\\s*" + name + "\\s*\\[\\s*" + - std::to_string(i) + "\\s*\\]"); - out = std::regex_replace(out, measureTargetRx, - "-> qbraid__creg__[" + - std::to_string(base + i) + "]"); - } - } - - // Replace the first declaration with the merged register. - out = std::regex_replace(out, cregDeclRx, - "creg qbraid__creg__[" + - std::to_string(totalBits) + "];", - std::regex_constants::format_first_only); - - // Remove the remaining original declarations. - for (size_t i = 1; i < cregs.size(); ++i) { - std::regex toRemove("creg\\s+" + cregs[i].first + - "\\s*\\[\\s*\\d+\\s*\\]\\s*;\\s*"); - out = std::regex_replace(out, toRemove, ""); - } - - cudaq::info( - "Normalized {} classical registers into single qbraid__creg__[{}]", - cregs.size(), totalBits); - return out; - } - /// @brief Returns the headers for the server requests. RestHeaders getHeaders() override { if (backendConfig.find("api_key") == backendConfig.end()) { diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml index da3f92dc94d..2b83c3ea7ff 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/qbraid.yml @@ -15,8 +15,13 @@ config: gen-target-backend: true # Add the rest-qpu library to the link list link-libs: ["-lcudaq-rest-qpu"] - # Define the JIT lowering pipeline - jit-mid-level-pipeline: "qbraid-gate-set-mapping" + # Define the JIT lowering pipeline. Mirrors Braket so that + # `combine-measurements` runs and attaches the QIROutputNamesAttrName + # metadata nvq++ needs to thread user-visible qubit indices through to + # `outputNames[jobId]` in the helper. Without that pass the default register + # would contain compiler-generated ancillae and named sub-registers would be + # unreachable (see QbraidServerHelper::processResults). + jit-mid-level-pipeline: "lower-to-cfg,decomposition{basis=h,s,t,rx,ry,rz,x,y,z,x(1)},quake-to-cc-prep,func.func(expand-control-veqs,combine-quantum-alloc,canonicalize,combine-measurements)" # Tell the rest-qpu that we are generating OpenQASM. codegen-emission: qasm2 # Library mode is only for simulators, physical backends must turn this off diff --git a/unittests/backends/CMakeLists.txt b/unittests/backends/CMakeLists.txt index 130fc4ecb0b..627ae6a7395 100644 --- a/unittests/backends/CMakeLists.txt +++ b/unittests/backends/CMakeLists.txt @@ -97,7 +97,9 @@ if (OPENSSL_FOUND AND CUDAQ_ENABLE_PYTHON AND CUDAQ_TEST_MOCK_SERVERS) if (CUDAQ_ENABLE_SCALEWAY_BACKEND) add_subdirectory(scaleway) endif() - add_subdirectory(qbraid) + if (CUDAQ_ENABLE_QBRAID_BACKEND) + add_subdirectory(qbraid) + endif() add_subdirectory(extra_payload_provider) add_subdirectory(quake_backend) endif() diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp index ff59dbdd728..a73b0dc6da3 100644 --- a/unittests/backends/qbraid/QbraidTester.cpp +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -276,6 +276,19 @@ CUDAQ_TEST(QbraidTester, checkResultNotFound) { "result not found")); } +// 409 on /result -> terminal. qBraid v2 returns this when the job reached a +// non-success terminal state (FAILED or CANCELLED), so results will never +// appear and the helper must fail fast instead of burning the retry budget. +CUDAQ_TEST(QbraidTester, checkResultConflict) { + armResultStatus(409); + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + EXPECT_TRUE(throwsWithMessage([&]() { (void)cudaq::sample(kernel); }, + "did not produce results")); +} + // 500 on /result -> retryable. Force hook fires once then clears, so the // second attempt succeeds. Sampling must not throw. CUDAQ_TEST(QbraidTester, checkResultServerErrorRetries) { From 2b65005cfd0717c05043d45b14e4a4d83127b723 Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Sat, 9 May 2026 10:00:26 +0000 Subject: [PATCH 17/18] fix: login order for integration tests, ref PR #4486 Signed-off-by: TheGupta2012 --- .github/workflows/integration_tests.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 7297e2faeba..8f27dfcaf38 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -96,6 +96,13 @@ jobs: cudaq_test_image: ${{ steps.vars.outputs.cudaq_nightly_image }}@${{ steps.test_image.outputs.digest }} steps: + - name: Log in to GitHub CR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + - name: Set variables id: vars run: | @@ -113,13 +120,6 @@ jobs: echo "platforms=$(echo $platforms | tr ' ' ,)" >> $GITHUB_OUTPUT echo "cudaq_nightly_image=$cudaq_nightly_image" >> $GITHUB_OUTPUT - - name: Log in to GitHub CR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ github.token }} - - name: Set up context for buildx run: | docker context create builder_context From 875d33a4dac8904b84edba1690ae582b211898b7 Mon Sep 17 00:00:00 2001 From: TheGupta2012 Date: Wed, 13 May 2026 05:19:18 +0000 Subject: [PATCH 18/18] fix: ci failures Signed-off-by: TheGupta2012 --- .../default/rest/helpers/qbraid/QbraidServerHelper.cpp | 1 + unittests/backends/qbraid/QbraidTester.cpp | 1 + utils/mock_qpu/qbraid/__init__.py | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp index 5996d1ffe64..c981c055b4a 100644 --- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp +++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp @@ -11,6 +11,7 @@ #include "cudaq/Support/Version.h" #include "cudaq/runtime/logger/logger.h" #include "cudaq/utils/cudaq_utils.h" +#include #include #include diff --git a/unittests/backends/qbraid/QbraidTester.cpp b/unittests/backends/qbraid/QbraidTester.cpp index a73b0dc6da3..9ce3e6d49c6 100644 --- a/unittests/backends/qbraid/QbraidTester.cpp +++ b/unittests/backends/qbraid/QbraidTester.cpp @@ -12,6 +12,7 @@ #include "cudaq/algorithm.h" #include #include +#include #include // Update the backend string to match the QBraid format diff --git a/utils/mock_qpu/qbraid/__init__.py b/utils/mock_qpu/qbraid/__init__.py index 066650fac86..7fea100bec5 100644 --- a/utils/mock_qpu/qbraid/__init__.py +++ b/utils/mock_qpu/qbraid/__init__.py @@ -215,12 +215,12 @@ async def armDelayResults(count: int = Path(...)): # Test-only: force the next GET /result call to return the given HTTP status. # Consumed after one call. @app.post("/test/force_next_result_status/{code}") -async def armForceResultStatus(code: int = Path(...)): +async def forceNextResultStatus(code: int = Path(...)): FORCE_NEXT_RESULT_STATUS["code"] = code return {"armed_status": code} -# Test-only: reset all test-hook globals so tests are order-independent. +# Test-only: reset all test-hook state so tests are order-independent. @app.post("/test/reset") async def resetTestState(): FAIL_NEXT_JOB["enabled"] = False