From 8a421d5a85e4402f1c18113c53c8bf2946b8fa70 Mon Sep 17 00:00:00 2001 From: Jason Saroni Date: Wed, 3 Jun 2026 23:45:02 -0400 Subject: [PATCH 1/4] Implement estimator interface --- samples/estimator_test.cpp | 55 ++++++++ src/primitives/backend_estimator_v2.hpp | 163 ++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 samples/estimator_test.cpp create mode 100644 src/primitives/backend_estimator_v2.hpp diff --git a/samples/estimator_test.cpp b/samples/estimator_test.cpp new file mode 100644 index 0000000..d546bbb --- /dev/null +++ b/samples/estimator_test.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include + +#include "circuit/quantumcircuit.hpp" +#include "primitives/backend_estimator_v2.hpp" +#include "service/qiskit_runtime_service_c.hpp" +#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}) + ); + + // Bell state + circ.h(0); + circ.cx(0, 1); + circ.measure(qreg, creg); + + // Use your environment/saved account + auto service = QiskitRuntimeService(); + + // Replace with an available backend/simulator + auto backend = service.backend("ibm_kingston"); + + auto transpiled_circ = transpile(circ, backend); + + std::vector> obs = { + {"ZZ", 1.0}, + {"XI", 0.5} + }; + + auto estimator = Estimator(backend, 1000); + auto results = estimator.run({EstimatorPub(transpiled_circ, obs, 1000)}); + + std::cout << "EV = " << results[0].ev + << " +/- " << results[0].stddev << std::endl; + + return 0; +} \ No newline at end of file diff --git a/src/primitives/backend_estimator_v2.hpp b/src/primitives/backend_estimator_v2.hpp new file mode 100644 index 0000000..ccc8d75 --- /dev/null +++ b/src/primitives/backend_estimator_v2.hpp @@ -0,0 +1,163 @@ +#ifndef __qiskitcpp_primitives_backend_estimator_v2_hpp__ +#define __qiskitcpp_primitives_backend_estimator_v2_hpp__ + +#include +#include +#include +#include +#include + +#include "circuit/quantumcircuit.hpp" +#include "compiler/transpiler.hpp" +#include "primitives/backend_sampler_v2.hpp" +#include "primitives/containers/sampler_pub.hpp" +#include "providers/backend.hpp" + +namespace Qiskit { +namespace primitives { + +struct EstimatorPub { + circuit::QuantumCircuit circuit; + std::vector> observables; + uint_t shots = 0; + + EstimatorPub() {} + + EstimatorPub( + circuit::QuantumCircuit& circ, + std::vector> obs, + uint_t in_shots = 0 + ) : circuit(circ), observables(obs), shots(in_shots) {} +}; + +struct EstimatorPubResult { + double ev = 0.0; + double stddev = 0.0; +}; + +class BackendEstimatorV2 { +protected: + uint_t shots_; + providers::BackendV2& backend_; + + static 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; + } + + static 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; + } + + static 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); + } + } + } + +public: + BackendEstimatorV2(providers::BackendV2& backend, uint_t shots = 1024) + : shots_(shots), backend_(backend) {} + + const providers::BackendV2& backend(void) const { + return backend_; + } + + std::vector run(std::vector pubs) { + std::vector results; + + BackendSamplerV2 sampler(backend_, shots_); + + for (auto& pub : pubs) { + double total_ev = 0.0; + double variance_accum = 0.0; + + for (auto& term : pub.observables) { + const std::string& pauli = term.first; + double coeff = term.second; + + auto meas_circ = pub.circuit.copy(); + + append_basis_rotation(meas_circ, pauli); + + // Basis rotations add gates, so transpile after adding them. + auto isa_circ = compiler::transpile(meas_circ, backend_); + + SamplerPub sampler_pub( + isa_circ, + pub.shots > 0 ? pub.shots : shots_ + ); + + auto job = sampler.run({sampler_pub}); + 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 (auto& b : bits) { + mean += pauli_eigenvalue_from_bitstring(pauli, b); + } + + mean /= static_cast(bits.size()); + + total_ev += coeff * mean; + + double var = + (1.0 - mean * mean) / static_cast(bits.size()); + + variance_accum += coeff * coeff * var; + } + + EstimatorPubResult r; + r.ev = total_ev; + r.stddev = std::sqrt(variance_accum); + + results.push_back(r); + } + + return results; + } +}; + +} // namespace primitives +} // namespace Qiskit + +#endif \ No newline at end of file From 6ab632c44604c32a8e0146ec42a0ba3500fb080d Mon Sep 17 00:00:00 2001 From: Jason Saroni <77505813+jsaroni@users.noreply.github.com> Date: Thu, 4 Jun 2026 00:40:47 -0400 Subject: [PATCH 2/4] On using measurements to a quantum circuit to BackendEstimatorV2 On using measurements to a quantum circuit. --- src/primitives/backend_estimator_v2.hpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/primitives/backend_estimator_v2.hpp b/src/primitives/backend_estimator_v2.hpp index ccc8d75..86fa194 100644 --- a/src/primitives/backend_estimator_v2.hpp +++ b/src/primitives/backend_estimator_v2.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -88,6 +89,12 @@ class BackendEstimatorV2 { } } + static void append_measurements(circuit::QuantumCircuit& circ, uint_t num_qubits) { + for (uint_t q = 0; q < num_qubits; q++) { + circ.measure(q, q); + } + } + public: BackendEstimatorV2(providers::BackendV2& backend, uint_t shots = 1024) : shots_(shots), backend_(backend) {} @@ -98,7 +105,6 @@ class BackendEstimatorV2 { std::vector run(std::vector pubs) { std::vector results; - BackendSamplerV2 sampler(backend_, shots_); for (auto& pub : pubs) { @@ -108,12 +114,13 @@ class BackendEstimatorV2 { for (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); + append_measurements(meas_circ, num_qubits); - // Basis rotations add gates, so transpile after adding them. auto isa_circ = compiler::transpile(meas_circ, backend_); SamplerPub sampler_pub( @@ -125,6 +132,7 @@ class BackendEstimatorV2 { auto primitive_result = job->result(); auto pub_result = primitive_result[0]; + auto bits = pub_result.data().get_bitstrings(); if (bits.empty()) { @@ -149,7 +157,6 @@ class BackendEstimatorV2 { EstimatorPubResult r; r.ev = total_ev; r.stddev = std::sqrt(variance_accum); - results.push_back(r); } @@ -160,4 +167,4 @@ class BackendEstimatorV2 { } // namespace primitives } // namespace Qiskit -#endif \ No newline at end of file +#endif From 78fda3b672270b551a8fe18574a6996c4a050897 Mon Sep 17 00:00:00 2001 From: Jason Saroni <77505813+jsaroni@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:01:41 -0400 Subject: [PATCH 3/4] Update samples/estimator_test.cpp Co-authored-by: Luciano Bello <766693+1ucian0@users.noreply.github.com> --- samples/estimator_test.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/samples/estimator_test.cpp b/samples/estimator_test.cpp index d546bbb..b27dfcd 100644 --- a/samples/estimator_test.cpp +++ b/samples/estimator_test.cpp @@ -1,3 +1,17 @@ +/* +# 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. +*/ + #include #include #include From 7e3075a8c726f5d0ad0eb83d45e087679518ae9f Mon Sep 17 00:00:00 2001 From: Jason Saroni <77505813+jsaroni@users.noreply.github.com> Date: Tue, 9 Jun 2026 07:48:04 -0400 Subject: [PATCH 4/4] Update README for sampler and estimator examples --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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=