---
title: executor_estimator (latest version)
description: API reference for qiskit_ibm_runtime.executor_estimator in the latest version of qiskit-ibm-runtime
source: https://quantum.cloud.ibm.com/docs/en/api/qiskit-ibm-runtime/executor-estimator
---

# Client-side Estimator

`qiskit_ibm_runtime.executor_estimator`

## Overview

[`qiskit_ibm_runtime.executor_estimator.Estimator`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator "qiskit_ibm_runtime.executor_estimator.Estimator") is an implementation of the Qiskit `EstimatorV2` interface built on top of the `Executor` primitive. It estimates expectation values of quantum observables by executing ISA circuits on an IBM Quantum backend.

The key difference between the legacy server-side [`Estimator`](/docs/api/qiskit-ibm-runtime/estimator#qiskit_ibm_runtime.Estimator "qiskit_ibm_runtime.Estimator") and this new implementation is that **all pre- and post-processing runs on the client machine**. This includes circuit preparation (twirling, gate folding, dynamical decoupling, and noise injection) as well as result post-processing (TREX rescaling, ZNE extrapolation, and PEC quasi-probability weighting). Running these steps locally provides faster debugging feedback and greater user control.

When a user submits a job through [`run()`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator.run "qiskit_ibm_runtime.executor_estimator.Estimator.run"), the underlying processing consists of:

1. Coercing the Primitive Unified Blocs (PUBs), resolving the resilience-level defaults, and determining the shot count.
2. Converting the PUBs into a [`QuantumProgram`](/docs/api/qiskit-ibm-runtime/quantum-program-quantum-program#qiskit_ibm_runtime.quantum_program.QuantumProgram "qiskit_ibm_runtime.quantum_program.QuantumProgram"), applying circuit transformations (twirling, gate folding, DD, noise injection) according to the specified options.
3. Calling `Executor` to submit the quantum program to the backend.
4. Upon job completion, estimating expectation values from the raw measurement data and applying error-mitigation post-processing as needed.

> **Note**
>
> For large or complex workloads, the client-side pre- and post-processing steps can be resource intensive and may cause a delay before the job is submitted. Set the `qiskit_ibm_runtime` logger to `INFO` to monitor client-side processing:
>
> ```python
> import logging
> logger = logging.getLogger("qiskit_ibm_runtime")
> logger.setLevel(logging.INFO)
> ```

## Basic usage

**Example 1 — Minimal (no error mitigation)**

```python
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.executor_estimator import Estimator

# Select a backend.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
observable = SparsePauliOp("ZZ")

# Transform the circuit and observable into ISA format.
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)
isa_obs = observable.apply_layout(isa_qc.layout)

estimator = Estimator(mode=backend)
estimator.options.resilience_level = 0
job = estimator.run([(isa_qc, isa_obs)])
result = job.result()
print(result[0].data.evs)   # expectation value
```

**Example 2 — Resilience level 2 (measurement error mitigation + gate-folding ZNE)**

```python
estimator = Estimator(mode=backend)
estimator.options.resilience_level = 2  # TREX + ZNE (gate folding)
job = estimator.run([(isa_qc, isa_obs)])
result = job.result()
print(result[0].data.evs)                 # zero-noise extrapolated expectation value
print(result[0].data.evs_noise_factors)   # raw values at each noise amplification level
```

**Example 3 — PEC (requires explicit noise learning)**

Unlike the legacy server-side implementation, this client-side [`Estimator`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator "qiskit_ibm_runtime.executor_estimator.Estimator") requires explicit noise learning for the error mitigation methods that need a noise model (PEC and PEA).

Use [`find_unique_layers()`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator.find_unique_layers "qiskit_ibm_runtime.executor_estimator.Estimator.find_unique_layers") to extract the unique gate layers from your PUBs, pass the layers to [`NoiseLearnerV3`](/docs/api/qiskit-ibm-runtime/noise-learner-v3-noise-learner-v3#qiskit_ibm_runtime.noise_learner_v3.NoiseLearnerV3 "qiskit_ibm_runtime.noise_learner_v3.NoiseLearnerV3") to learn their noise in a separate job, then assign the learned noise maps to [`layer_noise_model`](/docs/api/qiskit-ibm-runtime/options-models-resilience-options#qiskit_ibm_runtime.options_models.ResilienceOptions.layer_noise_model "qiskit_ibm_runtime.options_models.ResilienceOptions.layer_noise_model").

```python
from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3

pubs = [(isa_qc, isa_obs)]

estimator = Estimator(mode=backend)
estimator.options.resilience.pec_mitigation = True

# Step 1. Extract the unique boxed gate layers from your PUBs.
layers = estimator.find_unique_layers(pubs)

# Step 2. Learn the noise model for those layers.
nl_result = NoiseLearnerV3(mode=backend).run(layers).result()

# Step 3. Convert the NoiseLearnerV3 result to Pauli-Lindblad maps and pass them
# to the Estimator. The maps follow the same order as the input layers, so they can
# be zipped positionally.
pauli_lindblad_maps = nl_result.to_pauli_lindblad_maps()
estimator.options.resilience.layer_noise_model = zip(layers, pauli_lindblad_maps)

job = estimator.run(pubs)
result = job.result()
print(result[0].data.evs)
```

## Inputs

Each call to [`run()`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator.run "qiskit_ibm_runtime.executor_estimator.Estimator.run") takes a list of PUBs. Each PUB is in this format:

```python
(<single circuit>, <one or more observables>, <optional parameter values>, <optional precision>)
```

See [Estimator inputs and outputs](/docs/guides/estimator-input-output) for more information on Estimator inputs and outputs.

Elements from observables and parameter values are combined by following NumPy broadcasting rules as described in [Primitive inputs and outputs](/docs/guides/primitive-input-output).

## Options

When instantiating [`Estimator`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator "qiskit_ibm_runtime.executor_estimator.Estimator"), you can pass in options by using [`EstimatorOptions`](/docs/api/qiskit-ibm-runtime/options-models-estimator-options#qiskit_ibm_runtime.options_models.EstimatorOptions "qiskit_ibm_runtime.options_models.EstimatorOptions") or a dictionary. Commonly used options, such as `resilience_level`, are at the first level. Other options are grouped into categories, such as `execution`. Specify the options in this format: `options.<category>.<option> = <value>`. For example: `options.dynamical_decoupling.enable = True`.

See [Introduction to options](/docs/guides/runtime-options-overview) for an overview on specifying primitive options. See [Specify Estimator options](/docs/guides/estimator-options) and [Configure noise management with Estimator](/docs/guides/estimator-noise-management) for more information about Estimator options.

## Outputs

[`run()`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator.run "qiskit_ibm_runtime.executor_estimator.Estimator.run") returns a [`RuntimeJobV2`](/docs/api/qiskit-ibm-runtime/runtime-job-v2#qiskit_ibm_runtime.RuntimeJobV2 "qiskit_ibm_runtime.RuntimeJobV2"). Calling `job.result()` returns a [`PrimitiveResult`](/docs/api/qiskit/qiskit.primitives.PrimitiveResult) of [`EstimatorPubResult`](/docs/api/qiskit-ibm-runtime/results-estimator-pub-result#qiskit_ibm_runtime.results.EstimatorPubResult "qiskit_ibm_runtime.results.EstimatorPubResult") objects — one per input PUB:

```python
result = job.result()
pub_result = result[0]        # EstimatorPubResult for the first PUB
pub_result.data               # DataBin holding numerical arrays
pub_result.metadata           # dictionary with per-PUB metadata
result.metadata               # dictionary with job-level metadata
```

The contents of `pub_result.data` depend on the mitigation technique specified.

### No mitigation / measurement mitigation only (resilience levels 0 and 1)

All of the following fields have the shape `pub_shape`, which is `broadcast(param_shape, obs_shape)`.

- `data.evs` — Expectation values.
- `data.stds` — Standard deviations. Reflects the spread across twirling randomizations when twirling is enabled; equals `ensemble_standard_error` when twirling is disabled.
- `data.ensemble_standard_error` — Standard error under the independently and identically-distributed shot-noise assumption (no drift contribution).

When measurement mitigation (TREX) is active, the `evs` values are corrected for readout errors using a calibration circuit that is run automatically alongside the main circuits.

### PEC

PEC produces the same three fields as the no-mitigation case: `evs`, `stds`, and `ensemble_standard_error`. The gamma quasi-probability factor is applied internally during post-processing and does not appear as a separate output field.

The `stds` values are scaled by the gamma factor, so they are typically larger than in the no-mitigation case for the same shot count. This is the fundamental cost of PEC: unbiased estimates come with increased variance proportional to the sampling overhead (`gamma^2`).

### ZNE (gate folding) and PEA

When `resilience.zne_mitigation=True`, the estimator runs the circuit at multiple noise amplification levels and fits a curve to extrapolate to zero noise. The result contains both the extrapolated estimate and the raw data at each noise level.

- `data.evs` — Zero-noise extrapolated expectation values (best heterogeneous fit — the extrapolator is chosen per term for multi-term observables). Shape: `pub_shape`.
- `data.stds` — Standard deviations of the extrapolated values. Same shape as `evs`. Derived from the spread over twirling randomizations when twirling is on.
- `data.evs_noise_factors` — Raw (non-extrapolated) expectation values at each noise amplification level. Shape: `(*pub_shape, num_noise_factors)`.
- `data.stds_noise_factors` — Standard deviations at each noise factor. Same shape as `evs_noise_factors`. Reflects the spread over twirling randomizations when twirling is on; equals `ensemble_stds_noise_factors` when twirling is off.
- `data.ensemble_stds_noise_factors` — Ensemble standard errors at each noise factor under the independently and identically-distributed shot-noise assumption. Shape: `(*pub_shape, num_noise_factors)`.
- `data.evs_extrapolated` — Expectation values from each requested extrapolator, evaluated at each point in `resilience.zne.extrapolated_noise_factors`. These are forced homogeneous fits — the same extrapolator is applied to all terms of a multi-term observable — one fit per extrapolator. Shape: `(*pub_shape, num_extrapolators, num_eval_points)`.
- `data.stds_extrapolated` — Standard deviations corresponding to `evs_extrapolated`. Same shape.

> **Note**
>
> For multi-term observables (for example, `{"XX": 0.5, "XY": 0.5}`), `evs` and `stds` use a heterogeneous fit: the best-fitting extrapolator is selected independently for each Pauli term. `evs_extrapolated` and `stds_extrapolated` use a homogeneous fit per extrapolator, which is useful for comparing models. If your analysis needs a single extrapolator applied consistently, split the multi-term observable into single-term observables so that each term is fit on its own.

ZNE results can be visualized with [`draw_zne_evs()`](/docs/api/qiskit-ibm-runtime/results-estimator-pub-result#qiskit_ibm_runtime.results.EstimatorPubResult.draw_zne_evs "qiskit_ibm_runtime.results.EstimatorPubResult.draw_zne_evs") and [`draw_zne_extrapolators()`](/docs/api/qiskit-ibm-runtime/results-estimator-pub-result#qiskit_ibm_runtime.results.EstimatorPubResult.draw_zne_extrapolators "qiskit_ibm_runtime.results.EstimatorPubResult.draw_zne_extrapolators") (requires `plotly`).

### Job-level metadata

`result.metadata` is a `dict` containing:

- `"options"` — the finalized [`EstimatorOptions`](/docs/api/qiskit-ibm-runtime/options-models-estimator-options#qiskit_ibm_runtime.options_models.EstimatorOptions "qiskit_ibm_runtime.options_models.EstimatorOptions"), as a dictionary. Inactive resilience sub-options are pruned (for example, the `zne` sub-dictionary is omitted when `zne_mitigation=False`), and noise model (if provided) is removed.
- `"target_precision"` — the precision resolved from the PUBs and the `precision` argument of [`run()`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator.run "qiskit_ibm_runtime.executor_estimator.Estimator.run"), or `None` if neither specified one. In that case, the shot count comes from `default_shots`, falling back to `default_precision`.
- `"shots"` — the total shot count used for execution.
- `"executor"` — the metadata of the underlying Executor result.

## Migration guide

This client-side Estimator implementation is largely a drop-in replacement for the legacy one. Follow the steps listed below to migrate to the new implementation, keeping in mind these behavioral changes:

- **Pre- and post-processing happen on the client side.** In the legacy Estimator, all circuit transformations (twirling, gate folding, and noise injection), as well as result post-processing (ZNE extrapolation, TREX rescaling, and PEC weighting), are performed on the server side. In this client-side implementation, these steps run entirely on the client machine.
- **Noise learning is a separate step.** In the legacy Estimator, noise learning for PEC and PEA is integrated into the Estimator job itself and handled implicitly on the server side. In this new implementation, noise learning is a separate workflow that must be performed explicitly.
- **Executor jobs are submitted.** The client-side Estimator uses Executor to submit jobs, making them Executor jobs rather than Estimator jobs. Job attributes, such as [`primitive_id`](/docs/api/qiskit-ibm-runtime/runtime-job-v2#qiskit_ibm_runtime.RuntimeJobV2.primitive_id "qiskit_ibm_runtime.RuntimeJobV2.primitive_id") and [`inputs`](/docs/api/qiskit-ibm-runtime/runtime-job-v2#qiskit_ibm_runtime.RuntimeJobV2.inputs "qiskit_ibm_runtime.RuntimeJobV2.inputs"), return information about the Executor job.

**Step 1 — Update the imports.**

**Before:**

```python
from qiskit_ibm_runtime import Estimator
from qiskit_ibm_runtime.options import EstimatorOptions
```

**After:**

```python
from qiskit_ibm_runtime.executor_estimator import Estimator
from qiskit_ibm_runtime.options_models import EstimatorOptions
```

**Step 2 — Perform noise learning explicitly (if using PEC or PEA).**

If your code uses PEC (`pec_mitigation=True`) or ZNE with PEA (`zne.amplifier="pea"`), you need to perform noise learning explicitly by using `NoiseLearnerV3`.

**Before:**

```python
from qiskit_ibm_runtime import Estimator

pubs = [...]  # Your PUBs
estimator = Estimator(mode=backend)
estimator.options.resilience.pec_mitigation = True  # or zne_mitigation + pea amplifier

job = estimator.run(pubs)
```

**After:**

```python
from qiskit_ibm_runtime.executor_estimator import Estimator
from qiskit_ibm_runtime import NoiseLearnerV3

pubs = [...]  # Your PUBs
estimator = Estimator(mode=backend)
estimator.options.resilience.pec_mitigation = True  # or zne_mitigation + pea amplifier

# Identify the unique layers to learn.
layers = estimator.find_unique_layers(pubs)

# Learn the noise model for those layers (runs as a separate job).
learner = NoiseLearnerV3(mode=backend)
learner_job = learner.run(layers)
learner_result = learner_job.result()

# Convert the result to Pauli-Lindblad noise maps.
pauli_lindblad_maps = learner_result.to_pauli_lindblad_maps()

# Assign the learned noise maps so PEA/PEC uses them.
# The result objects returned by NoiseLearnerV3 follow the same order as the input
# layers, so the positional zip can be used here.
estimator.options.resilience.layer_noise_model = zip(layers, pauli_lindblad_maps)

# Now execute the target PUBs.
job = estimator.run(pubs)
```

**Step 3 — Split one job into several (if the PUBs use different precision values).**

Mixed precisions are no longer supported: a job can no longer contain PUBs that request different precision values, and [`run()`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator.run "qiskit_ibm_runtime.executor_estimator.Estimator.run") raises `IBMInputValueError` if it does. Group the PUBs by precision and submit one job per group, using a [`Batch`](/docs/api/qiskit-ibm-runtime/batch#qiskit_ibm_runtime.Batch "qiskit_ibm_runtime.Batch") so the groups still run together.

**Before:**

```python
from qiskit_ibm_runtime import Estimator

# PUBs with different precision values.
pubs = [(isa_circuit, isa_obs, None, 0.1), (isa_circuit1, isa_obs1, None, 0.5)]
estimator = Estimator(mode=backend)

job = estimator.run(pubs)
```

**After:**

```python
from qiskit_ibm_runtime.executor_estimator import Estimator
from qiskit_ibm_runtime import Batch

# Group the PUBs by precision, one group per job.
with Batch(backend=backend) as batch:
    estimator = Estimator(mode=batch)

    jobs = [
        estimator.run([(isa_circuit, isa_obs)], precision=0.1),
        estimator.run([(isa_circuit1, isa_obs1)], precision=0.5),
    ]

results = [job.result() for job in jobs]
```

## Classes

|                                                                                                                                                                                     |                                                                                                                                                                                               |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`Estimator`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator "qiskit_ibm_runtime.executor_estimator.Estimator")          | Client-side Estimator primitive for IBM Quantum Compute (formerly Qiskit Runtime).                                                                                                            |
| [`EstimatorV2`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator-v2#qiskit_ibm_runtime.executor_estimator.EstimatorV2 "qiskit_ibm_runtime.executor_estimator.EstimatorV2") | alias of [`Estimator`](/docs/api/qiskit-ibm-runtime/executor-estimator-estimator#qiskit_ibm_runtime.executor_estimator.Estimator "qiskit_ibm_runtime.executor_estimator.estimator.Estimator") |
