diff --git a/README.md b/README.md index 8e5c06b..535fa25 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ $ cmake .. $ make ``` +If you want to use [SQC C API](https://github.com/jhpc-quantum/SQC), follow the SQC documentation. + ### Building Qiskit C++ Qiskit C++ only has C++ header files. There is nothing to do to build the SDK. @@ -93,18 +95,24 @@ $ cmake -DQISKIT_ROOT=Path_to_qiskit .. $ make ``` -If you want to build sampler or transpiler example, you will need one of qiskit-ibm-runtime C or QRMI. +If you want to build sampler or transpiler example, you will need one of qiskit-ibm-runtime C or QRMI or SQC. -Then example can be built by setting `QISKIT_IBM_RUNTIME_C_ROOT` or `QRMI_ROOT` to cmake. +Then example can be built by setting `QISKIT_IBM_RUNTIME_C_ROOT` or `QRMI_ROOT` or `SQC_ROOT` to cmake. ```shell-session $ cd samples $ mkdir build $ cd build -$ cmake -DQISKIT_ROOT=Path_to_qiskit -DQISKIT_IBM_RUNTIME_C_ROOT="path to qiskit-ibm-runtime C" or -DQRMI_ROOT="path to QRMI" .. +$ cmake -DQISKIT_ROOT=Path_to_qiskit -DQISKIT_IBM_RUNTIME_C_ROOT="path to qiskit-ibm-runtime C" or -DQRMI_ROOT="path to QRMI" or -DSQC_ROOT="path to SQC" .. $ make ``` +You also need to set the library options to the environment variable `SQC_LIBS` before cmake if you use SQC. The format of `SQC_LIBS` is assumued to be as follows, for example. For required options, please refer the the SQC documentation. + +``` +"-lsqc_api -lsqc_rpc ... -pthread" +``` + To run sampler example, set your account information in `$HOME/.qiskit/qiskit-ibm.json` (see https://github.com/Qiskit/qiskit-ibm-runtime?tab=readme-ov-file#save-your-account-on-disk) or setting following environment variables to access Quantum hardware. ``` diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 01dcbe4..b69cb01 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -36,6 +36,8 @@ function(add_application APP_NAME CPP_FILE) PUBLIC ${QISKIT_ROOT}/target/release ${QRMI_ROOT}/target/release + ${SQC_ROOT}/lib + ${SQC_ROOT}/lib64 ${QISKIT_IBM_RUNTIME_C_ROOT}/build/cargo/debug ) if(QRMI_ROOT) @@ -44,6 +46,8 @@ function(add_application APP_NAME CPP_FILE) elseif(QISKIT_IBM_RUNTIME_C_ROOT) target_link_libraries(${APP_NAME} qiskit_cext.dll.lib qiskit_ibm_runtime.dll.lib nlohmann_json::nlohmann_json) set(CMAKE_CXX_FLAGS "-DQISKIT_IBM_RUNTIME_C_ROOT=${QISKIT_IBM_RUNTIME_C_ROOT}") + #else(SQC_ROOT) + # SQC for MSVC is not supported else() target_link_libraries(${APP_NAME} qiskit_cext.dll.lib nlohmann_json::nlohmann_json) target_compile_options(${APP_NAME} PRIVATE "-DQISKIT_IBM_RUNTIME_C_ROOT=${QISKIT_IBM_RUNTIME_C_ROOT}") @@ -61,6 +65,12 @@ function(add_application APP_NAME CPP_FILE) "-L${QISKIT_ROOT}/dist/c/lib -L${QISKIT_IBM_RUNTIME_C_ROOT}/build/cargo/debug -Wl,-rpath ${QISKIT_ROOT}/dist/c/lib -Wl,-rpath ${QISKIT_IBM_RUNTIME_C_ROOT}/build/cargo/debug" qiskit qiskit_ibm_runtime nlohmann_json::nlohmann_json ) target_compile_options(${APP_NAME} PRIVATE "-DQISKIT_IBM_RUNTIME_C_ROOT=${QISKIT_IBM_RUNTIME_C_ROOT}") + elseif(SQC_ROOT) + target_link_libraries(${APP_NAME} + PRIVATE + "-L${QISKIT_ROOT}/dist/c/lib -L${SQC_ROOT}/lib -L${SQC_ROOT}/lib64 -Wl,-rpath ${QISKIT_ROOT}/dist/c/lib -Wl,-rpath ${SQC_ROOT}/lib -Wl,-rpath ${SQC_ROOT}/lib64" qiskit nlohmann_json::nlohmann_json $ENV{SQC_LIBS} + ) + target_compile_options(${APP_NAME} PRIVATE "-DSQC_ROOT=${SQC_ROOT}") else() target_link_libraries(${APP_NAME} PRIVATE @@ -75,6 +85,7 @@ function(add_application APP_NAME CPP_FILE) ${QISKIT_ROOT}/dist/c/include ${QRMI_ROOT} ${QISKIT_IBM_RUNTIME_C_ROOT}/include + ${SQC_ROOT}/include ${SAMPLES_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/../src nlohmann_json::nlohmann_json @@ -91,7 +102,7 @@ endfunction() add_application(circuit_test circuit_test.cpp) add_application(observable_test observable_test.cpp) -if(QRMI_ROOT OR QISKIT_IBM_RUNTIME_C_ROOT) +if(QRMI_ROOT OR QISKIT_IBM_RUNTIME_C_ROOT OR SQC_ROOT) add_application(sampler_test sampler_test.cpp) add_application(transpile_test transpile_test.cpp) endif() diff --git a/src/providers/sqc_backend.hpp b/src/providers/sqc_backend.hpp new file mode 100644 index 0000000..6699186 --- /dev/null +++ b/src/providers/sqc_backend.hpp @@ -0,0 +1,156 @@ +/* +# This code is part of Qiskit. +# +# (C) Copyright IBM 2025. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +*/ + +// SQC Backend + +#ifndef __qiskitcpp_providers_SQC_backend_def_hpp__ +#define __qiskitcpp_providers_SQC_backend_def_hpp__ + +#include +#include + +#include "utils/types.hpp" +#include "transpiler/target.hpp" +#include "primitives/containers/sampler_pub.hpp" +#include "providers/sqc_job.hpp" + +#include "sqc_ecode.h" +#include "sqc_api.h" + +namespace Qiskit { +namespace providers { + +std::string replace_all(std::string s, const std::string& from, const std::string& to); + +/// @class SQCBackend +/// @brief Backend class using SQC. +class SQCBackend : public BackendV2 { +private: + const sqcBackend backend_type_; + std::shared_ptr target_; + +public: + /// @brief Create a new SQCBackend. Internally this initializes SQC. + SQCBackend() + : SQCBackend("unspecified") + {} + + /// @brief Create a new SQCBackend object + /// @param backend_name a resource name for backend. + SQCBackend(const std::string name) + : BackendV2(name), + backend_type_(SQC_RPC_SCHED_QC_TYPE_IBM_DACC), + target_(nullptr) + {} + + SQCBackend(const SQCBackend& other) + : BackendV2(other.name_), + backend_type_(other.backend_type_), + target_(other.target_) + {} + + ~SQCBackend() {} + + /// @brief Return a target properties for this backend. + /// @return a target class (nullptr) + std::shared_ptr target(void) override + { + if(target_) return target_; + + // Create a dummy circuit to get target json files + std::unique_ptr qc_handle(sqcQuantumCircuit(0), &sqcDestroyQuantumCircuit); + if(sqcIbmdTranspileInfo(qc_handle.get(), backend_type_) != SQC_RESULT_OK) { + std::cerr << "Failed to get the target information" << std::endl; + return nullptr; + } + + nlohmann::ordered_json target_json; + target_json["configuration"] = nlohmann::ordered_json::parse(qc_handle->backend_config_json); + target_json["properties"] = nlohmann::ordered_json::parse(qc_handle->backend_props_json); + auto target = std::make_shared(); + if(!target->from_json(target_json)) { + std::cerr << "Failed to create a target from json files" << std::endl; + return nullptr; + } + target_ = target; + + return target_; + } + + /// @brief Run and collect samples from each pub. + /// @return SQCJob + std::shared_ptr run(std::vector& input_pubs, uint_t shots) override + { + auto circuit = input_pubs[0].circuit(); + const auto qasm3_str = circuit.to_qasm3(); + std::cout << "run qasm3: \n" << qasm3_str << std::endl; + + // special modification of QASM3 for SQC + std::string sqc_qasm3_str = qasm3_str; + static const std::regex re(R"(\r\n|\r|\n)"); + sqc_qasm3_str = std::regex_replace(sqc_qasm3_str, re, std::string("\\n")); + sqc_qasm3_str = replace_all(sqc_qasm3_str, "\"", "\\\""); + sqc_qasm3_str.insert(0, "\""); + sqc_qasm3_str.append("\""); + std::cout << "qasm3 for SQC: \n" << sqc_qasm3_str << std::endl; + + std::shared_ptr sqc_circ(sqcQuantumCircuit(circuit.num_qubits()), sqcDestroyQuantumCircuit); + sqc_circ->qasm = strdup(qasm3_str.c_str()); + + std::unique_ptr run_options(new sqcRunOptions); + sqcInitializeRunOpt(run_options.get()); + run_options->nshots = shots; + run_options->qubits = sqc_circ->qubits; + run_options->outFormat = SQC_OUT_RAW; // Currently SQC supports the raw format only + + std::shared_ptr result((sqcOut*)malloc(sizeof(sqcOut)), [](sqcOut* out) { sqcFreeOut(out, SQC_OUT_RAW); }); + int error_code = sqcQCRun(sqc_circ.get(), backend_type_, *run_options, result.get()); + + if(error_code != SQC_RESULT_OK) + { + std::cerr << "Error: Failed to run a SQC circuit." << std::endl; + return nullptr; + } + + auto results_json = nlohmann::ordered_json::parse(result->result); + + return std::make_shared(results_json); + } +}; + + +std::string replace_all(std::string s, const std::string& from, const std::string& to) { + if (from.empty()) return s; + std::string out; + out.reserve(s.size()); + std::size_t pos = 0; + while (true) { + std::size_t found = s.find(from, pos); + if (found == std::string::npos) { + out.append(s, pos, std::string::npos); + break; + } + out.append(s, pos, found - pos); + out.append(to); + pos = found + from.size(); + } + return out; +} + + +} // namespace providers +} // namespace Qiskit + + +#endif //__qiskitcpp_providers_SQC_backend_def_hpp__ diff --git a/src/providers/sqc_job.hpp b/src/providers/sqc_job.hpp new file mode 100644 index 0000000..61ad051 --- /dev/null +++ b/src/providers/sqc_job.hpp @@ -0,0 +1,104 @@ +/* +# This code is part of Qiskit. +# +# (C) Copyright IBM 2025. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +*/ + +// Job class for SQC + +#ifndef __qiskitcpp_providers_SQC_job_hpp__ +#define __qiskitcpp_providers_SQC_job_hpp__ + +#include + +#include "utils/types.hpp" + +#include "primitives/containers/sampler_pub_result.hpp" +#include "providers/job.hpp" + +namespace Qiskit { +namespace providers { + +/// @class SQCJob +/// @brief Job class for SQC +class SQCJob : public Job { +private: + std::string job_id_; + nlohmann::ordered_json results_; // json formatted results (converted output from SQC) + uint_t num_results_ = 0; + +public: + /// @brief Create a new SQCBackend + SQCJob() + : SQCJob(std::string{""}) + {} + + /// @brief Create a new SQCBackend object + SQCJob(const std::string& job_id) + : job_id_(job_id), + num_results_(0) + {} + + /// @note [TODO] This constructor will be removed after SQC API provides a async job execution + SQCJob(const nlohmann::ordered_json results) + : job_id_(""), + num_results_(results["results"].size()), + results_(results) + {} + + /// @brief Create a new SQCJob from other + SQCJob(const SQCJob& other) + { + job_id_ = other.job_id_; + num_results_ = other.num_results_; + results_ = other.results_; + } + + ~SQCJob() {} + + /// @brief Return the status of the job. + /// @return JobStatus enum. + providers::JobStatus status(void) override + { + /// @todo Wait SQC API for making the status request API public. + return providers::JobStatus::DONE; + } + + + /// @brief Return the number of results in this job + /// @return number of results + uint_t num_results(void) override + { + return num_results_; + } + + /// @brief get sampler pub result + /// @param index an index of the result + /// @param result an output sampler pub result + /// @return true if result is successfully set + bool result(uint_t index, primitives::SamplerPubResult& result) override + { + if (index >= num_results_) + return false; + + result.from_json(results_["results"][index]); + + return true; + } +}; + +} // namespace providers +} // namespace Qiskit + + +#endif //__qiskitcpp_providers_SQC_job_hpp__ + + diff --git a/src/service/qiskit_runtime_service.hpp b/src/service/qiskit_runtime_service.hpp index a82d1fe..1ac27e1 100644 --- a/src/service/qiskit_runtime_service.hpp +++ b/src/service/qiskit_runtime_service.hpp @@ -17,6 +17,8 @@ #ifdef QRMI_ROOT #include "service/qiskit_runtime_service_qrmi.hpp" +#elif defined(SQC_ROOT) +#include "service/qiskit_runtime_service_sqc.hpp" #else // otherwise use Qiskit IBM runtime C-API #include "service/qiskit_runtime_service_c.hpp" #endif diff --git a/src/service/qiskit_runtime_service_sqc.hpp b/src/service/qiskit_runtime_service_sqc.hpp new file mode 100644 index 0000000..df93c7f --- /dev/null +++ b/src/service/qiskit_runtime_service_sqc.hpp @@ -0,0 +1,59 @@ +/* +# This code is part of Qiskit. +# +# (C) Copyright IBM 2025. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +*/ + +// Qiskit Runtime Service for SQC + +#ifndef __qiskitcpp_providers_qiskit_runtime_service_SQC_def_hpp__ +#define __qiskitcpp_providers_qiskit_runtime_service_SQC_def_hpp__ + +#include "providers/sqc_backend.hpp" + +namespace Qiskit { +namespace service { + +class QiskitRuntimeService { +private: + sqcInitOptions* init_options_; + +public: + /// @brief Create a new runtime service class + QiskitRuntimeService() + : init_options_(NULL) + { + init_options_ = sqcMallocInitOptions(); + init_options_->use_qiskit = 1; // only ibm_dacc is supported + if(sqcInitialize(init_options_) != E_SUCCESS) { + std::cerr << "Failed to initialize SQC" << std::endl; + } + } + + ~QiskitRuntimeService() + { + sqcFinalize(init_options_); + sqcFreeInitOptions(init_options_); + } + + /// @brief Create a new backend object + /// @param name the name of the backend resource + /// @return A new QkrtBackend class + Qiskit::providers::SQCBackend backend(std::string name) + { + return Qiskit::providers::SQCBackend(name); + } +}; + +} // namespace service +} // namespace Qiskit + +#endif