Skip to content
Closed
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
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 @@ -109,7 +109,7 @@ $ cmake -DQISKIT_ROOT=Path_to_qiskit -DQISKIT_IBM_RUNTIME_C_ROOT="path to qiskit
$ make
```

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.
To run sampler or estimator examples, 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. For the estimator sample, build with `-DQRMI_ROOT=...`.

```
QISKIT_IBM_TOKEN=<your API key>
Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/add-backend-estimator-v2-145.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
features:
- |
Add ``BackendEstimatorV2``, ``EstimatorPub``, and ``BackendEstimatorJob`` for
IBM Runtime Estimator V2 via QRMI. ``EstimatorPub`` accepts ``SparseObservable``
inputs, and ``QRMIBackend`` submits native ``estimator`` program jobs on IQP.
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()
78 changes: 78 additions & 0 deletions samples/estimator_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# 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 via QRMI Runtime V2

#include <iostream>
#include <cstdint>

#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::primitives;
using namespace Qiskit::service;
using namespace Qiskit::compiler;
using namespace Qiskit::quantum_info;

using Estimator = BackendEstimatorV2;

int main()
{
int num_qubits = 2;
QuantumCircuit circ(num_qubits, 0);

circ.h(0);
circ.cx(0, 1);

auto service = QiskitRuntimeService();
auto backend = service.backend("ibm_torino");
auto estimator = Estimator(backend, 0.02);

auto transpiled_circ = transpile(circ, backend);

std::vector<std::pair<std::string, std::complex<double>>> terms;
terms.push_back(std::make_pair(std::string("ZZ"), std::complex<double>(1.0)));
terms.push_back(std::make_pair(std::string("XX"), std::complex<double>(0.5)));
auto observable = SparseObservable::from_list(terms, num_qubits);
observable = observable.apply_layout(transpiled_circ.get_qubit_map());

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

auto result = job->result();
auto pub_result = result[0];

std::cout << "===== estimator result for pub[0] =====" << std::endl;
if (!pub_result.evs().empty()) {
std::cout << "evs: " << pub_result.evs()[0] << std::endl;
}
if (!pub_result.stds().empty()) {
std::cout << "stds: " << pub_result.stds()[0] << std::endl;
}
if (pub_result.metadata().contains("shots")) {
std::cout << "Shots: " << pub_result.metadata()["shots"] << std::endl;
}
if (pub_result.metadata().contains("target_precision")) {
std::cout << "Target precision: " << pub_result.metadata()["target_precision"] << std::endl;
}

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

/// @brief get qubits to be measured
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.
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
151 changes: 151 additions & 0 deletions src/primitives/backend_estimator_job.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2024.
#
# 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 <memory>
#include <stdexcept>
#include <thread>
#include <vector>

#include "primitives/containers/estimator_result.hpp"
#include "providers/job.hpp"
#include "providers/jobstatus.hpp"

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:
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();
}
}

const std::vector<EstimatorPub>& pubs(void) const
{
return pubs_;
}

providers::JobStatus status(void)
{
if (!job_) {
return providers::JobStatus::ERROR;
}
return job_->status();
}

bool running(void)
{
return status() == providers::JobStatus::RUNNING;
}

bool queued(void)
{
return status() == providers::JobStatus::QUEUED;
}

bool done(void)
{
return status() == providers::JobStatus::DONE;
}

bool cancelled(void)
{
return status() == providers::JobStatus::CANCELLED;
}

bool in_final_state(void)
{
return is_final_state(status());
}

bool cancel(void)
{
return false;
}

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__
99 changes: 99 additions & 0 deletions src/primitives/backend_estimator_v2.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2024.
#
# 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.
*/

// estimator implementation for a backend

#ifndef __qiskitcpp_primitives_backend_estimator_v2_def_hpp__
#define __qiskitcpp_primitives_backend_estimator_v2_def_hpp__

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

#include "primitives/backend_estimator_job.hpp"
#include "primitives/containers/estimator_pub.hpp"
#include "providers/backend.hpp"

namespace Qiskit {
namespace primitives {

/// @class BackendEstimatorV2
/// @brief Implementation of EstimatorV2 on a backend.
class BackendEstimatorV2 {
protected:
double default_precision_;
providers::BackendV2& backend_;

public:
BackendEstimatorV2(providers::BackendV2& backend, double default_precision = 0.015625)
: default_precision_(default_precision), backend_(backend)
{
if (!valid_precision(default_precision_)) {
throw std::invalid_argument("BackendEstimatorV2 default precision must be finite and non-negative.");
}
}

const providers::BackendV2& backend(void) const
{
return backend_;
}

double default_precision(void) const
{
return default_precision_;
}

std::shared_ptr<BackendEstimatorJob> run(std::vector<EstimatorPub> pubs)
{
return run(pubs, default_precision_);
}

std::shared_ptr<BackendEstimatorJob> run(std::vector<EstimatorPub> pubs, double precision)
{
if (!valid_precision(precision)) {
throw std::invalid_argument("Estimator run precision must be finite and non-negative.");
}

for (uint_t i = 0; i < pubs.size(); i++) {
std::string message;
if (!pubs[i].validate(&message)) {
throw std::invalid_argument(message);
}
if (pubs[i].circuit().has_measurements()) {
throw std::invalid_argument("Estimator circuits must not contain measurement operations.");
}
if (!pubs[i].has_precision()) {
pubs[i].set_precision(precision);
}
}

auto job = backend_.run(pubs, precision);
if (!job) {
return nullptr;
}
return std::make_shared<BackendEstimatorJob>(job, pubs);
}

protected:
static bool valid_precision(double precision)
{
return std::isfinite(precision) && precision >= 0.0;
}
};

} // namespace primitives
} // namespace Qiskit

#endif //__qiskitcpp_primitives_backend_estimator_v2_def_hpp__
Loading