Skip to content

Commit bdb24ce

Browse files
authored
Add support for ibm-qiskit-runtime C (#118)
* refactor sources for abstraction of IQP APIs, add support for qiskit-ibm-runtime C * fix test
1 parent b5ce024 commit bdb24ce

18 files changed

Lines changed: 891 additions & 289 deletions

CMakeLists.txt

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ else()
1212
endif()
1313

1414
set(QISKIT_CPP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/)
15-
#set(QISKIT_CPP_RUST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/crates/qc_rust)
16-
# set(QC_TRANSPILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/crates/qc_transpile)
17-
#set(QISKIT_CPP_STATEVECTOR_SAMPLER_PATH ${CMAKE_CURRENT_SOURCE_DIR}/crates/qc_statevector)
18-
#set(SAMPLES_PATH ${CMAKE_CURRENT_SOURCE_DIR}/samples/)
19-
#set(EXTENSIONS_PATH ${CMAKE_CURRENT_SOURCE_DIR}/demo/)
2015

2116
set(THREADS_PREFER_PTHREAD_FLAG ON)
2217
find_package(Threads REQUIRED)
@@ -41,21 +36,34 @@ function(add_application APP_NAME CPP_FILE)
4136
PUBLIC
4237
${QISKIT_ROOT}/target/release
4338
${QRMI_ROOT}/target/release
39+
${QISKIT_IBM_RUNTIME_C_ROOT}/build/cargo/debug
4440
)
4541
if(QRMI_ROOT)
4642
target_link_libraries(${APP_NAME} qiskit_cext.dll.lib qrmi.dll.lib nlohmann_json::nlohmann_json)
43+
target_compile_options(${APP_NAME} PRIVATE "-DQRMI_ROOT=${QRMI_ROOT}")
44+
elseif(QISKIT_IBM_RUNTIME_C_ROOT)
45+
target_link_libraries(${APP_NAME} qiskit_cext.dll.lib qiskit_ibm_runtime.dll.lib nlohmann_json::nlohmann_json)
46+
set(CMAKE_CXX_FLAGS "-DQISKIT_IBM_RUNTIME_C_ROOT=${QISKIT_IBM_RUNTIME_C_ROOT}")
4747
else()
4848
target_link_libraries(${APP_NAME} qiskit_cext.dll.lib nlohmann_json::nlohmann_json)
49+
target_compile_options(${APP_NAME} PRIVATE "-DQISKIT_IBM_RUNTIME_C_ROOT=${QISKIT_IBM_RUNTIME_C_ROOT}")
4950
endif()
5051
else()
5152
if(QRMI_ROOT)
5253
target_link_libraries(${APP_NAME}
53-
PUBLIC
54+
PRIVATE
5455
"-L${QISKIT_ROOT}/dist/c/lib -L${QRMI_ROOT}/target/release -Wl,-rpath ${QISKIT_ROOT}/dist/c/lib -Wl,-rpath ${QRMI_ROOT}/target/release" qiskit qrmi nlohmann_json::nlohmann_json
5556
)
57+
target_compile_options(${APP_NAME} PRIVATE "-DQRMI_ROOT=${QRMI_ROOT}")
58+
elseif(QISKIT_IBM_RUNTIME_C_ROOT)
59+
target_link_libraries(${APP_NAME}
60+
PRIVATE
61+
"-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
62+
)
63+
target_compile_options(${APP_NAME} PRIVATE "-DQISKIT_IBM_RUNTIME_C_ROOT=${QISKIT_IBM_RUNTIME_C_ROOT}")
5664
else()
5765
target_link_libraries(${APP_NAME}
58-
PUBLIC
66+
PRIVATE
5967
"-L${QISKIT_ROOT}/dist/c/lib -Wl,-rpath ${QISKIT_ROOT}/dist/c/lib" qiskit nlohmann_json::nlohmann_json
6068
)
6169
endif()
@@ -66,6 +74,7 @@ function(add_application APP_NAME CPP_FILE)
6674
PRIVATE
6775
${QISKIT_ROOT}/dist/c/include
6876
${QRMI_ROOT}
77+
${QISKIT_IBM_RUNTIME_C_ROOT}/include
6978
${SAMPLES_PATH}
7079
${CMAKE_CURRENT_SOURCE_DIR}/src
7180
nlohmann_json::nlohmann_json
@@ -83,7 +92,7 @@ endfunction()
8392
add_application(circuit_test tests/circuit_test.cpp)
8493
add_application(observable_test tests/observable_test.cpp)
8594

86-
if(QRMI_ROOT)
95+
if(QRMI_ROOT OR QISKIT_IBM_RUNTIME_C_ROOT)
8796
add_application(sampler_test tests/sampler_test.cpp)
8897
add_application(transpile_test tests/transpile_test.cpp)
8998
endif()

README.md

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,44 @@ This interface is based on Qiskit C-API introduced in Qiskit 2.1.
3434

3535
### Preparing Qiskit and Qiskit C extension
3636

37-
Before building Qiskit C++, install Qiskit 2.1 or later and build Qiskit C extension library https://github.com/Qiskit/qiskit/tree/main/crates/cext
37+
Before building Qiskit C++, install Qiskit 2.2 or later and build Qiskit C extension library https://github.com/Qiskit/qiskit/tree/main/crates/cext
3838

3939
```shell-session
4040
$ git clone git@github.com:Qiskit/qiskit.git
4141
$ cd qiskit
4242
$ make c
4343
```
4444

45+
### Preparing Interface API to Quantum hardware
46+
47+
Qiskit C++ requires one of the following APIs to access IBM Quantum Platform to run the quantum circuits.
48+
49+
- qiskit-ibm-runtime C (https://github.com/Qiskit/qiskit-ibm-runtime-c)
50+
- QRMI (https://github.com/qiskit-community/qrmi)
51+
52+
Before building your application with Qiskit C++, build one of these APIs.
53+
54+
55+
If you want to use QRMI, build as following.
56+
57+
```shell-session
58+
$ git clone git@github.com:qiskit-community/qrmi.git
59+
$ cd qrmi
60+
$ cargo build --release
61+
```
62+
63+
If you want to use qiskit-ibm-runtime C, build as following.
64+
(Note, qiskit-ibm-runtime C is an early prototype so build method may be changed)
65+
66+
```shell-session
67+
$ git clone git@github.com:Qiskit/qiskit-ibm-runtime-c
68+
$ cd qiskit-ibm-runtime-c
69+
$ mkdir build
70+
$ cd build
71+
$ cmake ..
72+
$ make
73+
```
74+
4575
### Building Qiskit C++
4676

4777
Qiskit C++ only has C++ header files. There is nothing to do to build the SDK.
@@ -62,24 +92,18 @@ $ cmake -DQISKIT_ROOT=Path_to_qiskit ..
6292
$ make
6393
```
6494

65-
If you want to build sampler example, you will need QRMI C-API.
66-
67-
```shell-session
68-
$ git clone git@github.com:qiskit-community/qrmi.git
69-
$ cd qrmi
70-
$ cargo build --release
71-
```
95+
If you want to build sampler/transpiler example, you will need one of qiskit-ibm-runtime C or QRMI.
7296

73-
Then example can be built by setting `QRMI_ROOT`,
97+
Then example can be built by setting `QISKIT_IBM_RUNTIME_C_ROOT` or `QRMI_ROOT` to cmake.
7498

7599
```shell-session
76100
$ mkdir build
77101
$ cd build
78-
$ cmake -DQISKIT_ROOT=Path_to_qiskit -DQRMI_ROOT=Path_to_QRMI ..
102+
$ cmake -DQISKIT_ROOT=Path_to_qiskit -DQISKIT_IBM_RUNTIME_C_ROOT="path to qiskit-ibm-runtime C" or -DQRMI_ROOT="path to QRMI" ..
79103
$ make
80104
```
81105

82-
To run sampler example, set following environment variables to access Quantum hardware.
106+
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.
83107

84108
```
85109
QISKIT_IBM_TOKEN=<your API key>
@@ -88,9 +112,10 @@ QISKIT_IBM_INSTANCE=<your CRN>
88112

89113
### Making your own interface to Quantum hardware
90114

91-
By default, Qiskit C++ uses QRMI (https://github.com/qiskit-community/qrmi) to access Quantum computers through IBM Qiskit Runtime Service.
92-
To use other services of interfaces, prepare your own backend interface from the BackendV2 base class (./src/providers/backend.hpp)
93-
Refer to ./src/providers/qrmi_backend.hpp for details.
115+
Qiskit C++ offers an abstract interface to access Quantum hardware. You can make your own interface to the hardware
116+
by overriding `providers::Job` and `providers::BackendV2` and make your own service provider to make backend object like
117+
`service::QiskitRuntimeService` class.
118+
Refer to `./src/providers/qrmi_backend.hpp` and `./src/providers/qrmi_job.hpp` for details.
94119

95120
## Contributing
96121

src/primitives/backend_sampler_job.hpp

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727

2828
#include "primitives/base/base_primitive_job.hpp"
2929
#include "providers/backend.hpp"
30-
31-
#include "qrmi.h"
30+
#include "providers/job.hpp"
3231

3332
namespace Qiskit {
3433
namespace primitives {
@@ -37,70 +36,103 @@ namespace primitives {
3736
/// @brief Job class for Backend Sampler primitive.
3837
class BackendSamplerJob : public BasePrimitiveJob {
3938
protected:
40-
providers::BackendV2& backend_;
39+
std::shared_ptr<providers::Job> job_ = nullptr;
4140
public:
4241
/// @brief Create a new BasePrimitiveJob
43-
/// @param backend a backend to be used in this job
44-
/// @param id a unique id identifying the job.
45-
BackendSamplerJob(providers::BackendV2& backend, std::string id, std::vector<SamplerPub>& pubs) : BasePrimitiveJob(id, pubs), backend_(backend) {}
42+
/// @param job a job pointer to run pubs
43+
/// @param pubs a list of pub
44+
BackendSamplerJob(std::shared_ptr<providers::Job> job, std::vector<SamplerPub>& pubs) : BasePrimitiveJob(pubs)
45+
{
46+
job_ = job;
47+
}
48+
49+
BackendSamplerJob(const BackendSamplerJob& other) : BasePrimitiveJob(other)
50+
{
51+
job_ = other.job_;
52+
}
4653

54+
~BackendSamplerJob()
55+
{
56+
if (job_)
57+
job_.reset();
58+
}
4759

4860
/// @brief Return the status of the job.
4961
/// @return JobStatus enum.
5062
providers::JobStatus status(void) override
5163
{
52-
return backend_.status(job_id_);
64+
return job_->status();
5365
}
5466

5567
/// @brief Return whether the job is actively running.
5668
/// @return true if job is actively running, otherwise false.
5769
bool running(void) override
5870
{
59-
return backend_.status(job_id_) == providers::JobStatus::RUNNING;
71+
return job_->status() == providers::JobStatus::RUNNING;
6072
}
6173

6274
/// @brief Return whether the job is queued.
6375
/// @return true if job is queued, otherwise false.
6476
bool queued(void)
6577
{
66-
return backend_.status(job_id_) == providers::JobStatus::QUEUED;
78+
return job_->status() == providers::JobStatus::QUEUED;
6779
}
6880

6981
/// @brief Return whether the job has successfully run.
7082
/// @return true if successfully run, otherwise false.
7183
bool done(void) override
7284
{
73-
return backend_.status(job_id_) == providers::JobStatus::DONE;
85+
return job_->status() == providers::JobStatus::DONE;
7486
}
7587

7688
/// @brief Return whether the job has been cancelled.
7789
/// @return true if job has been cancelled, otherwise false.
7890
bool cancelled(void) override
7991
{
80-
return backend_.status(job_id_) == providers::JobStatus::CANCELLED;
92+
return job_->status() == providers::JobStatus::CANCELLED;
8193
}
8294

8395
/// @brief Return whether the job is in a final job state such as DONE or ERROR.
8496
/// @return true if job is in a final job state, otherwise false.
8597
bool in_final_state(void) override
8698
{
87-
auto status = backend_.status(job_id_);
99+
auto status = job_->status();
88100
if (status == providers::JobStatus::DONE || status == providers::JobStatus::CANCELLED || status == providers::JobStatus::FAILED)
89101
return true;
90102
return false;
91103
}
92104

93-
94105
/// @brief Attempt to cancel the job.
95106
bool cancel(void) override
96107
{
97-
return backend_.stop_job(job_id_);
108+
//return job_.stop_job();
109+
return true;
98110
}
99111

100112
PrimitiveResult result(void) override
101113
{
102-
auto result = backend_.result(job_id_);
103-
result.set_pubs(pubs_);
114+
providers::JobStatus st;
115+
while (true) {
116+
st = job_->status();
117+
if (st == providers::JobStatus::DONE || st == providers::JobStatus::CANCELLED || st == providers::JobStatus::FAILED)
118+
break;
119+
#ifdef _MSC_VER
120+
Sleep(1);
121+
#else
122+
std::this_thread::sleep_for(std::chrono::seconds(1));
123+
#endif
124+
}
125+
126+
PrimitiveResult result;
127+
if (st == providers::JobStatus::DONE) {
128+
uint_t num_results = job_->num_results();
129+
result.allocate(num_results);
130+
131+
for (uint_t i = 0; i< num_results; i++) {
132+
result[i].set_pub(pubs_[i]);
133+
job_->result(i, result[i]);
134+
}
135+
}
104136
return result;
105137
}
106138
};

src/primitives/backend_sampler_v2.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ class BackendSamplerV2 {
5252
/// @return PrimitiveJob
5353
std::shared_ptr<BasePrimitiveJob> run(std::vector<SamplerPub> pubs)
5454
{
55-
return backend_.run(pubs, shots_);
55+
auto job = backend_.run(pubs, shots_);
56+
return std::make_shared<BackendSamplerJob>(job, pubs);
5657
}
5758

5859
};

src/primitives/base/base_primitive_job.hpp

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,18 @@ namespace primitives {
2727
/// @brief Primitive job abstract base class.
2828
class BasePrimitiveJob {
2929
protected:
30-
std::string job_id_;
3130
std::vector<SamplerPub> pubs_;
3231
public:
3332
/// @brief Create a new BasePrimitiveJob
3433
BasePrimitiveJob() {}
3534

3635
/// @brief Create a new BasePrimitiveJob
37-
/// @param id a unique id identifying the job.
38-
BasePrimitiveJob(std::string id, std::vector<SamplerPub>& pubs) : job_id_(id)
36+
/// @param pubs list of pub for this job
37+
BasePrimitiveJob(std::vector<SamplerPub>& pubs)
3938
{
4039
pubs_ = pubs;
4140
}
4241

43-
/// @brief Return a unique id identifying the job.
44-
/// @return a unique id identifying the job.
45-
std::string& job_id(void)
46-
{
47-
return job_id_;
48-
}
49-
5042
/// @brief get pubs for this job
5143
/// @return a list of pub
5244
const std::vector<SamplerPub>& pubs(void) const

src/primitives/containers/bit_array.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ class BitArray {
9898
/// @brief Set pub samples from json
9999
/// @param input JSON input
100100
void from_json(nlohmann::ordered_json& input);
101+
102+
/// @brief Set pub sample from hexstring
103+
/// @param index an index to be set
104+
/// @param input a sample in a hex string format
105+
void set_hexstring(uint_t index, std::string& input);
101106
};
102107

103108
void BitArray::from_samples(const reg_t& samples, uint_t num_bits)
@@ -167,6 +172,13 @@ void BitArray::from_json(nlohmann::ordered_json& input)
167172
}
168173
}
169174

175+
void BitArray::set_hexstring(uint_t index, std::string& input)
176+
{
177+
if (index < array_.size())
178+
array_[index].from_hex_string(input);
179+
}
180+
181+
170182

171183
} // namespace primitives
172184
} // namespace Qiskit

0 commit comments

Comments
 (0)