diff --git a/README.md b/README.md index ef34d34..ea0ed7f 100644 --- a/README.md +++ b/README.md @@ -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, estimator, 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` or `SQC_ROOT` to cmake. @@ -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. ``` QISKIT_IBM_TOKEN= diff --git a/releasenotes/notes/add-backend-estimator-v2-145.yaml b/releasenotes/notes/add-backend-estimator-v2-145.yaml new file mode 100644 index 0000000..0280aad --- /dev/null +++ b/releasenotes/notes/add-backend-estimator-v2-145.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``BackendEstimatorV2``, ``EstimatorPub``, and ``BackendEstimatorJob`` for + Pauli expectation-value estimation via the existing sampler interface. diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 8d49a60..3c560f9 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -106,5 +106,6 @@ add_application(parameterized_circuit_test parameterized_circuit_test.cpp) if(QRMI_ROOT OR QISKIT_IBM_RUNTIME_C_ROOT OR SQC_ROOT) add_application(sampler_test sampler_test.cpp) + add_application(estimator_test estimator_test.cpp) add_application(transpile_test transpile_test.cpp) endif() diff --git a/samples/estimator_test.cpp b/samples/estimator_test.cpp new file mode 100644 index 0000000..6fcadbb --- /dev/null +++ b/samples/estimator_test.cpp @@ -0,0 +1,72 @@ +/* +# 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 + +#include +#include + +#include "circuit/quantumcircuit.hpp" +#include "primitives/backend_estimator_v2.hpp" +#ifdef QRMI_ROOT +#include "service/qiskit_runtime_service_qrmi.hpp" +#else +#include "service/qiskit_runtime_service_c.hpp" +#endif + +#include "compiler/transpiler.hpp" + +using namespace Qiskit; +using namespace Qiskit::circuit; +using namespace Qiskit::primitives; +using namespace Qiskit::service; +using namespace Qiskit::compiler; + +using Estimator = BackendEstimatorV2; + +int main() +{ + int num_qubits = 2; + auto qreg = QuantumRegister(num_qubits); + auto creg = ClassicalRegister(num_qubits, std::string("meas")); + QuantumCircuit circ(std::vector({qreg}), + std::vector({creg})); + + circ.h(0); + circ.cx(0, 1); + circ.measure(qreg, creg); + + auto service = QiskitRuntimeService(); + auto backend = service.backend("ibm_torino"); + auto estimator = Estimator(backend, 1000); + + auto transpiled_circ = transpile(circ, backend); + + std::vector> observables = { + {"ZZ", 1.0}, + {"XI", 0.5}, + }; + + auto job = estimator.run({EstimatorPub(transpiled_circ, observables, 1000)}); + if (job == nullptr) { + return -1; + } + + auto result = job->result(); + auto pub_result = result[0]; + std::cout << "EV = " << pub_result.ev() + << " +/- " << pub_result.stddev() << std::endl; + + return 0; +} diff --git a/src/primitives/backend_estimator_job.hpp b/src/primitives/backend_estimator_job.hpp new file mode 100644 index 0000000..e3ba944 --- /dev/null +++ b/src/primitives/backend_estimator_job.hpp @@ -0,0 +1,219 @@ +/* +# 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__ + +#define _USE_MATH_DEFINES +#include +#include +#include +#include + +#include "compiler/transpiler.hpp" +#include "primitives/backend_sampler_v2.hpp" +#include "primitives/containers/estimator_pub.hpp" +#include "primitives/containers/estimator_result.hpp" +#include "primitives/containers/sampler_pub.hpp" +#include "providers/backend.hpp" +#include "providers/jobstatus.hpp" + +namespace Qiskit { +namespace primitives { + +namespace { + +int bit_value_from_string(const std::string& bitstring, uint_t qubit, uint_t num_qubits) +{ + uint_t pos = num_qubits - 1 - qubit; + return bitstring[pos] == '1' ? 1 : 0; +} + +double pauli_eigenvalue_from_bitstring(const std::string& pauli, const std::string& bitstring) +{ + double value = 1.0; + uint_t n = pauli.size(); + + for (uint_t q = 0; q < n; q++) { + char p = pauli[n - 1 - q]; + + if (p == 'I') { + continue; + } + + int bit = bit_value_from_string(bitstring, q, n); + value *= (bit == 0) ? 1.0 : -1.0; + } + + return value; +} + +void append_basis_rotation(circuit::QuantumCircuit& circ, const std::string& pauli) +{ + uint_t n = pauli.size(); + + for (uint_t q = 0; q < n; q++) { + char p = pauli[n - 1 - q]; + + if (p == 'X') { + circ.h(q); + } else if (p == 'Y') { + circ.rz(-M_PI / 2.0, q); + circ.h(q); + } + } +} + +void append_measurements(circuit::QuantumCircuit& circ, uint_t num_qubits) +{ + for (uint_t q = 0; q < num_qubits; q++) { + circ.measure(q, q); + } +} + +EstimatorPubResult estimate_pub(providers::BackendV2& backend, + uint_t default_shots, + EstimatorPub& pub) +{ + BackendSamplerV2 sampler(backend, default_shots); + double total_ev = 0.0; + double variance_accum = 0.0; + uint_t shots = pub.shots() > 0 ? pub.shots() : default_shots; + + for (const auto& term : pub.observables()) { + const std::string& pauli = term.first; + double coeff = term.second; + uint_t num_qubits = pauli.size(); + + auto meas_circ = pub.circuit().copy(); + append_basis_rotation(meas_circ, pauli); + + if (meas_circ.get_measure_map().empty()) { + append_measurements(meas_circ, num_qubits); + } + + auto isa_circ = compiler::transpile(meas_circ, backend); + SamplerPub sampler_pub(isa_circ, shots); + + auto job = sampler.run({sampler_pub}); + if (job == nullptr) { + continue; + } + + auto primitive_result = job->result(); + auto pub_result = primitive_result[0]; + auto bits = pub_result.data().get_bitstrings(); + + if (bits.empty()) { + continue; + } + + double mean = 0.0; + for (const auto& b : bits) { + mean += pauli_eigenvalue_from_bitstring(pauli, b); + } + mean /= static_cast(bits.size()); + + total_ev += coeff * mean; + variance_accum += coeff * coeff * (1.0 - mean * mean) / static_cast(bits.size()); + } + + EstimatorPubResult result(pub); + result.set_ev(total_ev); + result.set_stddev(std::sqrt(variance_accum)); + return result; +} + +} // namespace + +/// @class BackendEstimatorJob +/// @brief Job class for Backend Estimator primitive. +class BackendEstimatorJob { +protected: + providers::BackendV2& backend_; + uint_t shots_; + std::vector pubs_; + EstimatorResult result_; + bool finished_ = false; +public: + BackendEstimatorJob(providers::BackendV2& backend, + uint_t shots, + std::vector& pubs) + : backend_(backend), shots_(shots), pubs_(pubs) + { + } + + BackendEstimatorJob(const BackendEstimatorJob& other) + : backend_(other.backend_), + shots_(other.shots_), + pubs_(other.pubs_), + result_(other.result_), + finished_(other.finished_) + { + } + + const std::vector& pubs(void) const + { + return pubs_; + } + + providers::JobStatus status(void) + { + return finished_ ? providers::JobStatus::DONE : providers::JobStatus::RUNNING; + } + + bool running(void) + { + return !finished_; + } + + bool done(void) + { + return finished_; + } + + bool cancelled(void) + { + return false; + } + + bool in_final_state(void) + { + return finished_; + } + + bool cancel(void) + { + return false; + } + + EstimatorResult result(void) + { + if (!finished_) { + result_.allocate(pubs_.size()); + for (uint_t i = 0; i < pubs_.size(); i++) { + result_[i] = estimate_pub(backend_, shots_, pubs_[i]); + } + finished_ = true; + } + return result_; + } +}; + +} // namespace primitives +} // namespace Qiskit + +#endif //__qiskitcpp_primitives_backend_estimator_job_def_hpp__ diff --git a/src/primitives/backend_estimator_v2.hpp b/src/primitives/backend_estimator_v2.hpp new file mode 100644 index 0000000..db2ac18 --- /dev/null +++ b/src/primitives/backend_estimator_v2.hpp @@ -0,0 +1,63 @@ +/* +# 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 +#include + +#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: + uint_t shots_; + providers::BackendV2& backend_; +public: + /// @brief Create a new BackendEstimatorV2 + /// @param backend The backend to run circuits on + /// @param default_shots The default shots + BackendEstimatorV2(providers::BackendV2& backend, uint_t shots = 1024) + : shots_(shots), backend_(backend) + { + } + + /// @brief Return reference to backend object + const providers::BackendV2& backend(void) const + { + return backend_; + } + + /// @brief Run and collect expectation values from each pub. + /// @param pubs An iterable of estimator pub-like objects. + /// @return BackendEstimatorJob + std::shared_ptr run(std::vector pubs) + { + return std::make_shared(backend_, shots_, pubs); + } +}; + +} // namespace primitives +} // namespace Qiskit + +#endif //__qiskitcpp_primitives_backend_estimator_v2_def_hpp__ diff --git a/src/primitives/containers/estimator_pub.hpp b/src/primitives/containers/estimator_pub.hpp new file mode 100644 index 0000000..a9610d5 --- /dev/null +++ b/src/primitives/containers/estimator_pub.hpp @@ -0,0 +1,101 @@ +/* +# 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 pub definition + +#ifndef __qiskitcpp_primitives_estimator_pub_def_hpp__ +#define __qiskitcpp_primitives_estimator_pub_def_hpp__ + +#include +#include +#include +#include + +#include "circuit/quantumcircuit.hpp" + +namespace Qiskit { +namespace primitives { + +/// @class EstimatorPub +/// @brief Estimator Pub (Primitive Unified Bloc) with an ISA circuit and Pauli observables. +class EstimatorPub { +protected: + circuit::QuantumCircuit circuit_; + std::vector> observables_; + uint_t shots_ = 0; +public: + /// @brief Create a new EstimatorPub + EstimatorPub() {} + + /// @brief Create a new EstimatorPub + /// @param circ an ISA QuantumCircuit + /// @param observables Pauli-string observables as (label, coefficient) pairs + /// @param shots The total number of shots for this estimator pub + EstimatorPub(circuit::QuantumCircuit& circ, + std::vector> observables, + uint_t shots = 0) + { + circuit_ = circ; + observables_ = observables; + shots_ = shots; + } + + /// @brief Create a new EstimatorPub as a copy of src. + /// @param src an EstimatorPub + EstimatorPub(const EstimatorPub& src) + { + circuit_ = src.circuit_; + observables_ = src.observables_; + shots_ = src.shots_; + } + + ~EstimatorPub() {} + + /// @brief Return the QuantumCircuit for this estimator pub + const circuit::QuantumCircuit& circuit(void) const + { + return circuit_; + } + + /// @brief Return Pauli observables for this estimator pub + const std::vector>& observables(void) const + { + return observables_; + } + + /// @brief Return the total number of shots + uint_t shots(void) const + { + return shots_; + } + + /// @brief Return a JSON format of this estimator pub + nlohmann::ordered_json to_json(void) + { + nlohmann::ordered_json obs = nlohmann::json::array(); + for (const auto& term : observables_) { + obs.push_back(nlohmann::json::array({term.first, term.second})); + } + + if (shots_ > 0) { + return nlohmann::json::array({circuit_.to_qasm3(), obs, shots_}); + } + return nlohmann::json::array({circuit_.to_qasm3(), obs}); + } +}; + +} // namespace primitives +} // namespace Qiskit + +#endif //__qiskitcpp_primitives_estimator_pub_def_hpp__ diff --git a/src/primitives/containers/estimator_pub_result.hpp b/src/primitives/containers/estimator_pub_result.hpp new file mode 100644 index 0000000..e8f1290 --- /dev/null +++ b/src/primitives/containers/estimator_pub_result.hpp @@ -0,0 +1,78 @@ +/* +# 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 pub result class + +#ifndef __qiskitcpp_primitives_estimator_pub_result_hpp__ +#define __qiskitcpp_primitives_estimator_pub_result_hpp__ + +#include "primitives/containers/estimator_pub.hpp" + +namespace Qiskit { +namespace primitives { + +/// @class EstimatorPubResult +/// @brief Result of Estimator Pub (Primitive Unified Bloc). +class EstimatorPubResult { +protected: + EstimatorPub pub_; + double ev_ = 0.0; + double stddev_ = 0.0; +public: + EstimatorPubResult() {} + + EstimatorPubResult(const EstimatorPub& pub) : pub_(pub) {} + + EstimatorPubResult(const EstimatorPubResult& src) + { + pub_ = src.pub_; + ev_ = src.ev_; + stddev_ = src.stddev_; + } + + const EstimatorPub& pub(void) const + { + return pub_; + } + + void set_pub(const EstimatorPub& pub) + { + pub_ = pub; + } + + double ev(void) const + { + return ev_; + } + + double stddev(void) const + { + return stddev_; + } + + void set_ev(double ev) + { + ev_ = ev; + } + + void set_stddev(double stddev) + { + stddev_ = stddev; + } +}; + +} // namespace primitives +} // namespace Qiskit + +#endif //__qiskitcpp_primitives_estimator_pub_result_hpp__ diff --git a/src/primitives/containers/estimator_result.hpp b/src/primitives/containers/estimator_result.hpp new file mode 100644 index 0000000..fc423c2 --- /dev/null +++ b/src/primitives/containers/estimator_result.hpp @@ -0,0 +1,67 @@ +/* +# 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 primitive result class + +#ifndef __qiskitcpp_primitives_estimator_result_hpp__ +#define __qiskitcpp_primitives_estimator_result_hpp__ + +#include "primitives/containers/estimator_pub.hpp" +#include "primitives/containers/estimator_pub_result.hpp" + +namespace Qiskit { +namespace primitives { + +/// @class EstimatorResult +/// @brief A container for multiple estimator pub results. +class EstimatorResult { +protected: + std::vector pub_results_; +public: + EstimatorResult() {} + + void allocate(uint_t num_results) + { + pub_results_.resize(num_results); + } + + uint_t size(void) const + { + return pub_results_.size(); + } + + EstimatorPubResult& operator[](uint_t i) + { + return pub_results_[i]; + } + + const EstimatorPubResult& operator[](uint_t i) const + { + return pub_results_[i]; + } + + void set_pubs(const std::vector& pubs) + { + if (pubs.size() == pub_results_.size()) { + for (uint_t i = 0; i < pub_results_.size(); i++) { + pub_results_[i].set_pub(pubs[i]); + } + } + } +}; + +} // namespace primitives +} // namespace Qiskit + +#endif //__qiskitcpp_primitives_estimator_result_hpp__