Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Qiskit C++

Qiskit C++ provides a modern C++ (version C++ 11 or later) interface of Qiskit for circuit building, (transpilation and returning samples of quantum circuit outputs ... to be added in the future release), as same as Qiskit Python interfaces.
Qiskit C++ provides a modern C++ (version C++ 11 or later) interface of Qiskit for circuit building, transpilation, sampler jobs, and QRMI-backed estimator jobs, as same as Qiskit Python interfaces.
This interface is based on Qiskit C-API introduced in Qiskit 2.1.

## Supported OS
Expand Down Expand Up @@ -97,7 +97,7 @@ $ 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 or SQC.
If you want to build sampler or transpiler example, you will need one of qiskit-ibm-runtime C or QRMI or SQC. The estimator example currently requires QRMI.

Then example can be built by setting `QISKIT_IBM_RUNTIME_C_ROOT` or `QRMI_ROOT` or `SQC_ROOT` to cmake.

Expand All @@ -116,6 +116,8 @@ QISKIT_IBM_TOKEN=<your API key>
QISKIT_IBM_INSTANCE=<your CRN>
```

The estimator interface is available through `primitives::BackendEstimatorV2` and currently requires QRMI or another backend that implements `BackendV2::run(std::vector<EstimatorPub>&, double)`. Estimator PUB circuits must be unmeasured and can use Pauli labels, Pauli-label maps, Pauli term vectors, or `quantum_info::SparseObservable` inputs. Empty observables are rejected before backend submission, and malformed estimator result cardinality is rejected while reading provider results. Estimator job cancellation is not currently supported, so `BackendEstimatorJob::cancel()` returns `false`.

To run the sample example with SQC, you also need to set the library options to the environment variable `SQC_LIBS`, which is automatically set by a provided script (see [here](https://github.com/jhpc-quantum/documents/blob/main/SQC_JHPC_Quantum_user_guide.md)), before running cmake. In SQC 0.10.0, `backend_setup.sh` configures `SQC_LIBS` as follows:

```
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/backend-estimator-v2-145.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
features:
- |
Added `primitives::BackendEstimatorV2` with `EstimatorPub`,
`BackendEstimatorJob`, and estimator result containers. The QRMI backend
can now submit Runtime V2 `estimator` primitive jobs for unmeasured circuits
and Pauli observables, returning expectation values through
`EstimatorPubResult::evs()`. Empty observables are rejected before backend
submission, and estimator result parsing validates that provider `evs` and
`stds` cardinality matches the submitted PUB observables.
4 changes: 4 additions & 0 deletions samples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,7 @@ 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()

if(QRMI_ROOT)
add_application(estimator_test estimator_test.cpp)
endif()
77 changes: 77 additions & 0 deletions samples/estimator_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
# This code is part of Qiskit.
#
# (C) Copyright IBM 2026.
#
# 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.
*/

// Test program for estimator

#include <cstdint>
#include <cstdlib>
#include <complex>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#include "circuit/quantumcircuit.hpp"
#include "compiler/transpiler.hpp"
#include "primitives/backend_estimator_v2.hpp"
#include "quantum_info/sparse_observable.hpp"
#include "service/qiskit_runtime_service_qrmi.hpp"

using namespace Qiskit;
using namespace Qiskit::circuit;
using namespace Qiskit::compiler;
using namespace Qiskit::primitives;
using namespace Qiskit::quantum_info;
using namespace Qiskit::service;

using Estimator = BackendEstimatorV2;

int main()
{
QuantumCircuit circ(2, 0);
circ.h(0);
circ.cx(0, 1);

std::vector<std::pair<std::string, std::complex<double>>> terms;
terms.push_back(std::make_pair(std::string("ZZ"), std::complex<double>(1.0, 0.0)));
terms.push_back(std::make_pair(std::string("XX"), std::complex<double>(0.5, 0.0)));

auto service = QiskitRuntimeService();
auto backend = service.backend("ibm_kingston");
auto estimator = Estimator(backend);
auto transpiled_circ = transpile(circ, backend);
auto observable = SparseObservable::from_list(terms);
auto mapped_observable = observable.apply_layout(transpiled_circ.get_qubit_map());

auto job = estimator.run({EstimatorPub(transpiled_circ, mapped_observable)}, 0.02);
if (job == nullptr) {
return -1;
}

auto result = job->result();
auto evs = result[0].evs();
auto stds = result[0].stds();

std::cout << " ===== estimator result for pub[0] =====" << std::endl;
for (uint_t i = 0; i < evs.size(); i++) {
std::cout << "ev[" << i << "] = " << evs[i];
if (i < stds.size()) {
std::cout << ", std[" << i << "] = " << stds[i];
}
std::cout << std::endl;
}

return 0;
}
37 changes: 33 additions & 4 deletions src/circuit/quantumcircuit_def.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,20 @@ class QuantumCircuit
return measure_map_;
}

/// @brief get qubits to be measured
/// @return a set of qubits
const std::vector<std::pair<uint_t, uint_t>> &get_measure_map(void) const
{
return measure_map_;
}

/// @brief Return whether this circuit contains measurement operations.
/// @return true if there is at least one measurement operation.
bool has_measurements(void) const
{
return !measure_map_.empty();
}

/// @brief set global phase
/// @param phase global phase value
void global_phase(const double phase)
Expand Down Expand Up @@ -1840,10 +1854,25 @@ class QuantumCircuit

// Declare registers
// After transpilation, qubit registers will be mapped to physical registers,
// so we need to combined them in a single quantum register "q";
// so we need to combine them in a single quantum register "q" for ordinary
// circuits. Transpiled circuits should use physical qubit references.
const std::string qreg_name = "q";
qasm3 << "qubit[" << num_qubits() << "] " << qreg_name << ";" << std::endl;
const bool physical_qubits = !qubit_map_.empty();
auto qubit_ref = [physical_qubits, &qreg_name](uint_t index) -> std::string
{
if (physical_qubits) {
return std::string("$") + std::to_string(index);
}
return qreg_name + "[" + std::to_string(index) + "]";
};

if (!physical_qubits) {
qasm3 << "qubit[" << num_qubits() << "] " << qreg_name << ";" << std::endl;
}
for(const auto& creg : cregs_) {
if (creg.size() == 0) {
continue;
}
qasm3 << "bit[" << creg.size() << "] " << creg.name() << ";" << std::endl;
}

Expand All @@ -1863,7 +1892,7 @@ class QuantumCircuit
if (op->num_qubits == op->num_clbits) {
for (uint_t j = 0; j < op->num_qubits; j++) {
const auto creg_data = recover_reg_data(op->clbits[j]);
qasm3 << creg_data.first << "[" << creg_data.second << "] = " << op->name << " " << qreg_name << "[" << op->qubits[j] << "];" << std::endl;
qasm3 << creg_data.first << "[" << creg_data.second << "] = " << op->name << " " << qubit_ref(op->qubits[j]) << ";" << std::endl;
}
}
} else {
Expand All @@ -1886,7 +1915,7 @@ class QuantumCircuit
if (op->num_qubits > 0) {
qasm3 << " ";
for (uint_t j = 0; j < op->num_qubits; j++) {
qasm3 << qreg_name << "[" << op->qubits[j] << "]";
qasm3 << qubit_ref(op->qubits[j]);
if (j != op->num_qubits - 1)
qasm3 << ", ";
}
Expand Down
177 changes: 177 additions & 0 deletions src/primitives/backend_estimator_job.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2026.
#
# 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 BackendEstimator

#ifndef __qiskitcpp_primitives_backend_estimator_job_def_hpp__
#define __qiskitcpp_primitives_backend_estimator_job_def_hpp__

#include <chrono>
#include <thread>

#include <memory>
#include <stdexcept>
#include <vector>

#include "primitives/containers/estimator_primitive_result.hpp"
#include "providers/job.hpp"

// Windows headers can define ERROR after jobstatus.hpp has already been parsed.
#ifdef ERROR
#undef ERROR
#endif

namespace Qiskit {
namespace primitives {

/// @class BackendEstimatorJob
/// @brief Job class for Backend Estimator primitive.
class BackendEstimatorJob {
protected:
std::shared_ptr<providers::Job> job_ = nullptr;
std::vector<EstimatorPub> pubs_;

public:
/// @brief Create a new BackendEstimatorJob.
/// @param job a job pointer to run pubs.
/// @param pubs a list of pubs.
BackendEstimatorJob(std::shared_ptr<providers::Job> job, std::vector<EstimatorPub>& pubs)
{
job_ = job;
pubs_ = pubs;
}

BackendEstimatorJob(const BackendEstimatorJob& other)
{
job_ = other.job_;
pubs_ = other.pubs_;
}

~BackendEstimatorJob()
{
if (job_) {
job_.reset();
}
}

/// @brief get pubs for this job.
/// @return a list of pub.
const std::vector<EstimatorPub>& pubs(void) const
{
return pubs_;
}

/// @brief Return the status of the job.
/// @return JobStatus enum.
providers::JobStatus status(void)
{
if (!job_) {
return providers::JobStatus::ERROR;
}
return job_->status();
}

/// @brief Return whether the job is actively running.
/// @return true if job is actively running, otherwise false.
bool running(void)
{
return status() == providers::JobStatus::RUNNING;
}

/// @brief Return whether the job is queued.
/// @return true if job is queued, otherwise false.
bool queued(void)
{
return status() == providers::JobStatus::QUEUED;
}

/// @brief Return whether the job has successfully run.
/// @return true if successfully run, otherwise false.
bool done(void)
{
return status() == providers::JobStatus::DONE;
}

/// @brief Return whether the job has been cancelled.
/// @return true if job has been cancelled, otherwise false.
bool cancelled(void)
{
return status() == providers::JobStatus::CANCELLED;
}

/// @brief Return whether the job is in a final job state such as DONE or ERROR.
/// @return true if job is in a final job state, otherwise false.
bool in_final_state(void)
{
return is_final_state(status());
}

/// @brief Attempt to cancel the job.
/// @return false because backend cancellation is not implemented.
bool cancel(void)
{
return false;
}

/// @brief Return the results of the job.
/// @return estimator primitive result.
EstimatorPrimitiveResult result(void)
{
if (!job_) {
throw std::runtime_error("BackendEstimatorJob has no provider job.");
}

providers::JobStatus st;
while (true) {
st = job_->status();
if (is_final_state(st)) {
break;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}

if (st != providers::JobStatus::DONE) {
throw std::runtime_error("BackendEstimatorJob did not finish successfully.");
}

uint_t num_results = job_->num_results();
if (num_results != pubs_.size()) {
throw std::runtime_error("BackendEstimatorJob result count does not match the submitted pubs.");
}

EstimatorPrimitiveResult result;
result.allocate(num_results);
for (uint_t i = 0; i < num_results; i++) {
result[i].set_pub(pubs_[i]);
if (!job_->result(i, result[i])) {
throw std::runtime_error("BackendEstimatorJob failed to read an estimator pub result.");
}
}
return result;
}

protected:
static bool is_final_state(providers::JobStatus status)
{
return status == providers::JobStatus::DONE ||
status == providers::JobStatus::CANCELLED ||
status == providers::JobStatus::FAILED ||
status == providers::JobStatus::ERROR;
}
};

} // namespace primitives
} // namespace Qiskit

#endif //__qiskitcpp_primitives_backend_estimator_job_def_hpp__
Loading
Loading