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/samples/estimator_test.cpp b/samples/estimator_test.cpp new file mode 100644 index 0000000..b27dfcd --- /dev/null +++ b/samples/estimator_test.cpp @@ -0,0 +1,69 @@ +/* +# 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 +#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..86fa194 --- /dev/null +++ b/src/primitives/backend_estimator_v2.hpp @@ -0,0 +1,170 @@ +#ifndef __qiskitcpp_primitives_backend_estimator_v2_hpp__ +#define __qiskitcpp_primitives_backend_estimator_v2_hpp__ + +#include +#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); + } + } + } + + 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) {} + + 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; + uint_t num_qubits = pauli.size(); + + auto meas_circ = pub.circuit.copy(); + + append_basis_rotation(meas_circ, pauli); + append_measurements(meas_circ, num_qubits); + + 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