Skip to content

Issue #145 Implement BackendEstimatorV2 runtime support(with IBM Quantum Hardware test)#160

Merged
doichanj merged 4 commits into
Qiskit:mainfrom
GoiBasia:issue-145-estimator
Jun 9, 2026
Merged

Issue #145 Implement BackendEstimatorV2 runtime support(with IBM Quantum Hardware test)#160
doichanj merged 4 commits into
Qiskit:mainfrom
GoiBasia:issue-145-estimator

Conversation

@GoiBasia

@GoiBasia GoiBasia commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

This fix adds a native Backend Estimator V2 path to Qiskit C++ for issue #145. The core change is a typed estimator primitive implementation that can submit IBM Runtime V2 estimator jobs through QRMI using program_id = "estimator", instead of modeling estimator behavior as a sampler-like result or a raw vector wrapper.

The implementation introduces primitives::BackendEstimatorV2 as the user-facing entry point, along with BackendEstimatorJob, EstimatorPub, EstimatorPubResult, and EstimatorPrimitiveResult. It also extends BackendV2 with an estimator submission overload: run(std::vector<EstimatorPub>&, double). QRMI implements that overload by building a Runtime V2 estimator payload with version = 2, support_qiskit = false, an options object, and a pubs array whose entries carry the OpenQASM 3 circuit, observables, parameter values, and resolved target precision.

Details and comments

Job Handling

Users can construct estimator PUBs from Pauli labels, Pauli maps, Pauli term vectors, Runtime-compatible observable JSON, SparseObservable, or a vector of SparseObservable values.

The implementation rejects measured circuits before backend submission, both through BackendEstimatorV2::run() and the QRMI payload helper used by direct QRMI backend calls.

The estimator currently requires QRMI or another backend that implements BackendV2::run(std::vector<EstimatorPub>&, double). BackendEstimatorJob::cancel() returns false because estimator cancellation is not wired that it needs a provider-level contract, not just an estimator change.

BackendEstimatorJob::result() waits until the provider job reaches a final state, but only DONE can produce estimator results. FAILED, CANCELLED, and ERROR final states throw instead of returning partial or empty successful results. Provider result-count mismatches and provider read failures also throw.

QRMI result handling now caches the parsed result envelope, validates that the provider response contains a results array before indexing into it, and stops the QRMI task after reading results. This keeps the estimator path robust while preserving sampler QRMI cleanup behavior.

Files In Scope

The estimator-specific files are:

  • src/primitives/backend_estimator_v2.hpp
  • src/primitives/backend_estimator_job.hpp
  • src/primitives/containers/estimator_pub.hpp
  • src/primitives/containers/estimator_pub_result.hpp
  • src/primitives/containers/estimator_primitive_result.hpp
  • src/providers/qrmi_estimator_payload.hpp
  • test/test_estimator.cpp
  • samples/estimator_test.cpp
  • releasenotes/notes/backend-estimator-v2-145.yaml

The shared-file changes are in scope because estimator support needs them:

  • BackendV2 needs an estimator run() overload.
  • Job needs an estimator result overload.
  • QRMI backend/job code needs Runtime estimator submission and estimator result
    parsing.
  • QuantumCircuit needs measurement detection because estimator circuits must
    be rejected before submission.
  • SparseObservable needs safe copy/access helpers so estimator PUBs can
    serialize sparse observables into Runtime-compatible Pauli maps.
  • Sample CMake and README changes expose the new estimator example and document
    the current backend requirement.

Test Coverage

The estimator test suite covers:

  • PUB JSON shape and precision serialization.
  • QRMI Runtime V2 payload contract, including program_id = "estimator".
  • Default precision resolution and explicit PUB precision preservation.
  • Measured-circuit rejection before QRMI payload construction.
  • SparseObservable serialization.
  • Empty observable inputs and nested observable arrays.
  • Bad precision and width mismatch rejection.
  • Valid scalar and one-dimensional result shapes.
  • Too few and too many evs.
  • stds length mismatch.
  • Bad result metadata.
  • ERROR, FAILED, and CANCELLED provider states.
  • Provider read failures and result-count mismatches.

Verification Status

Local Test Operation

Commands:

cd qiskit-cpp

git diff --check
cmake -S test -B test/build
cmake --build test/build -j2
./test/build/test_driver test_estimator
ctest --test-dir test/build --output-on-failure

Run Successfully:

  • git diff --check exits with code 0.
  • CMake configures test/build after finding qiskit_cext.
  • test_driver test_estimator passes all estimator-focused tests.
  • ctest passes the full local test suite.
    Start 1: test_circuit
1/4 Test #1: test_circuit .....................   Passed    0.03 sec
    Start 2: test_estimator
2/4 Test #2: test_estimator ...................   Passed    0.03 sec
    Start 3: test_parameter
3/4 Test #3: test_parameter ...................   Passed    0.02 sec
    Start 4: test_transpiler
4/4 Test #4: test_transpiler ..................   Passed    0.04 sec

IBM Quantum Hardware Test

Run the hardware validation from the qiskit-cpp/samples/ directory after the local unit tests pass. This operation submits the estimator sample through QRMI to IBM Quantum Runtime.

Commands:

cd qiskit-cpp/samples

cmake -S . -B build \
  -DQISKIT_ROOT="$HOME/src/qiskit" \
  -DQRMI_ROOT="$HOME/src/qrmi"
cmake --build build -j2 --target estimator_test

export QISKIT_IBM_TOKEN=<your API key>
export QISKIT_IBM_INSTANCE=<your CRN>

./build/estimator_test

Run Successfully:

  • The sample builds the estimator_test executable.
  • QRMI connects to the selected IBM Quantum backend.
  • The sample submits a Runtime V2 estimator job, not a sampler job.
  • Hardware validation completed successfully on ibm_kingston with Runtime job
    status DONE.
  • Runtime metrics reported 12 quantum seconds.
  • The Runtime result metadata reported Estimator payload version = 2.
  • Runtime log: Executing estimator
  • The program printed the estimator result:
===== estimator result for pub[0] =====
evs: 1.4656326484378477
stds: 0.0080100055472789
ensemble_standard_error: 0.008611530628798818
Shots: 2528
Target precision: 0.02
Randomizations: 32

Submitted workload:

q0: ──H──■──
         │
q1: ─────X──

The current sample is submits an unmeasured circuit to Estimator and applies the transpiler layout to the observable terms before building the SparseObservable. That proves the hardware path is using a real Estimator V2 workload: an ISA-compatible circuit, layout-mapped observables, a native QRMI Runtime estimator submission, and a Runtime job that reached DONE on IBM hardware.

The sample submitted ZZ + 0.5 * XX as a SparseObservable after applying the transpiler layout to the observable terms. The hardware run used samples/estimator_test.cpp, backend ibm_kingston, target precision 0.02

Checklist

  • I have added the tests to cover my changes.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • Release note: releasenotes/notes/backend-estimator-v2-145.yaml
  • Unit tests under test/

@doichanj

doichanj commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

I have added one missing function in SparseObservable class to set layout for transpiled circuit.
I think this PR is well implemented and works correctly

@doichanj doichanj added enhancement New feature or request unitaryHACK26 labels Jun 9, 2026
@GoiBasia

GoiBasia commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

I have added one missing function in SparseObservable class to set layout for transpiled circuit. I think this PR is well implemented and works correctly

Thank you very much for adding SparseObservable::apply_layout the right place to handle observables for transpiled circuits, since Estimator inputs need the observable layout to match the ISA/transpiled circuit.

I also checked the remaining Windows CI failure. It looks unrelated to the estimator payload logic: the primitive job header included <windows.h> only for Sleep(1), which can collide with JobStatus::ERROR through the Windows ERROR macro. I replaced that with std::this_thread::sleep_for in the primitive job headers and verified the local test-driver build. I’ll push the update and rerun CI.

@1ucian0 1ucian0 linked an issue Jun 9, 2026 that may be closed by this pull request
@GoiBasia

GoiBasia commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Thank you very much for the review.

I pushed a follow-up fix 7693f92 for the Windows ERROR macro collision. Could a maintainer please approve the pending GitHub Actions workflow for this PR so tests-qiskit-cpp-windows-latest can rerun.

The fix is limited to Windows header macro hygiene and should not affect the estimator implementation or the SparseObservable::apply_layout change. And I’d like to confirm tests-qiskit-cpp-windows-latest passes.

@doichanj doichanj left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this PR looks good for me and well implemented.
Thank you for your contribution

@doichanj doichanj merged commit 1c8e1ca into Qiskit:main Jun 9, 2026
6 checks passed
@GoiBasia

GoiBasia commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

I think this PR looks good for me and well implemented.
Thank you for your contribution

Thank you for the review and kind words! I appreciate it. I’m glad the implementation looks good and address anything else if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request unitaryHACK26

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implementing estimator interface

3 participants