Skip to main content

Cram sheet — Section 6: Use the estimator primitive

One-screen review layer for this section: short background primers per objective, then the key facts — each backed by an official source or by an executed proof from the question bank.

Set estimator primitive options such as resilience levels​

The resilience_level dial: what 0, 1 and 2 buy​

resilience_level is one coarse dial that abstracts away the choice of method, leaving a cost/accuracy trade. The validator accepts only 0, 1 and 2; a string "2" or 2.0 is silently coerced.

LevelWhat it appliesCost
0no mitigation at allnone
1 (the default)readout mitigation via Twirled Readout Error eXtinction (TREX): measurement twirling plus an inversion of the diagonalized readout-error transfer matrixminimal
2everything level 1 does, PLUS zero-noise extrapolation and gate twirlingmedium; typically reduces bias, but "not guaranteed to be zero-bias"

There is no level 3, and no level turns on PEC — only resilience.pec_mitigation does. A higher level post-processes a larger ensemble of related circuits: more accurate, less biased expectation values for longer processing time and more QPU usage. It never makes a job cheaper, faster or shallower, nor changes which physical qubits you land on.

Options you set are applied IN ADDITION to the level's base set: level 0 turns zne_mitigation off, yet an explicit resilience.zne_mitigation = True re-enables ZNE.

Suppression versus mitigation:

FamilyWhen it actsExamplesHow you switch it on
error suppressionbefore or during execution, to stop errors happeningdynamical decoupling pulses idling qubits so their idle-time errors approximately canceloptions.dynamical_decoupling.enable
error mitigationafter execution, post-processing an ensemble of circuits to remove bias from expectation valuesTREX, ZNE, PEA, PECresilience_level or resilience.*_mitigation
neither—a higher transpiler optimization level; more shots, which shrink the STATISTICAL error, not the systematic bias—

Sources: estimator-noise-management · error-mitigation-and-suppression-techniques

The EstimatorOptions tree and its defaults​

EstimatorOptions is a nested dataclass; most option questions test nesting DEPTH.

LevelFields
topmax_execution_time, environment, simulator, default_precision, default_shots, resilience_level, seed_estimator, dynamical_decoupling, resilience, execution, twirling, experimental
resiliencemeasure_mitigation, measure_noise_learning, zne_mitigation, zne, pec_mitigation, pec, layer_noise_learning, layer_noise_model
resilience.zneamplifier, noise_factors, extrapolator, extrapolated_noise_factors

Three mistakes: the WRONG NAME (zne.factors, the Mitiq-flavoured zne.scale_factors), the right name at the WRONG LEVEL, and CONFIGURING WITHOUT ENABLING. Setting resilience.zne.noise_factors is accepted but leaves zne_mitigation at Unset, so the job runs without ZNE; resilience.zne_mitigation.enable = True raises nothing — Unset is a plain singleton, so the write lands harmlessly on it.

TechniqueTurned on only by
ZNEresilience.zne_mitigation = True (or resilience_level = 2)
TREXresilience.measure_mitigation = True
PECresilience.pec_mitigation = True

Documented defaults ("what if I touch nothing"):

OptionDefault
resilience_level1
resilience.measure_mitigationTrue
resilience.zne_mitigation, resilience.pec_mitigationFalse
resilience.zne.noise_factors(1, 3, 5)
resilience.zne.amplifiergate_folding
resilience.zne.extrapolator(exponential, linear)
dynamical_decoupling.enable, sequence_typeFalse, XX
twirling.enable_gates, twirling.enable_measureFalse, True
default_precision0.015625 — exactly 1/sqrt(4096)
max_execution_time10800 seconds

An untouched option sits at Unset: the server default applies. Set values with an EstimatorOptions instance or nested dict at construction, attribute-path assignment, or a bulk options.update(...).

Sources: estimator-options · runtime-options-overview

Must know:

  • ZNE runs the circuit at amplified noise rates (digital gate folding) and extrapolates to the zero-noise limit; it "is not guaranteed to produce an unbiased result". — 📖 error-mitigation-and-suppression-techniques
  • PEC writes the ideal circuit as a quasi-probability combination of noisy ones: unbiased, but its sampling overhead scales quadratically in gamma = sum of |quasi-probabilities|, which grows exponentially with depth. — 📖 error-mitigation-and-suppression-techniques
  • PEC is incompatible with gate-folding ZNE, PEA and fractional gates; enabling pec_mitigation with zne_mitigation raises a validation error. — 📖 estimator-options
  • default_precision sets error-bar length: 0.1 gives stds around 0.04–0.10 on a fake backend, 0.01 around 0.002–0.009. options.execution.shots is no route — execution holds only init_qubits and rep_delay. — ⚙️ proven in s6-q041

Traps:

  • resilience_level values 3, 4, 5, -1 and 1.5 all raise a pydantic ValidationError about the 0..2 range. — ⚙️ proven in s6-q014
  • ZNE scale factors live at options.resilience.zne.noise_factors; zne.factors (wrong name) and resilience.noise_factors (wrong level) are no_such_attribute errors. — ⚙️ proven in s6-q020
  • default_precision must be strictly positive: 0 raises ValidationError whether assigned or passed as EstimatorOptions(default_precision=0). — ⚙️ proven in s6-q031
  • EstimatorV2.run() takes only precision=; shots=, default_precision= and resilience_level= raise TypeError: unexpected keyword argument, and options.precision a ValidationError. — ⚙️ proven in s6-q016

Understand the theoretical background behind the estimator primitive​

The Estimator PUB: four positional slots​

An Estimator PUB is a tuple of AT MOST FOUR values in positional slots, never matched by content.

SlotContentsNote
1one QuantumCircuitmay contain Parameter objects
2an array of one or more observablesanything ObservablesArrayLike: Pauli, SparsePauliOp, PauliList or a bare string
3parameter values to bindits LAST index runs over the circuit's parameters; omit it or pass None when the circuit has none
4a target precision (optional)a float dropped into slot 3 gives ValueError: Length of () inconsistent with last dimension of [0.01]
result = est.run([(isa, isa_obs, params, 0.01)]) # not est.run((isa, isa_obs))

NumPy broadcasting of the observables array against the parameter-value array decides the output: one expectation value per element of the broadcast shape.

ObservablesParameter valuesevs.shape
one observablenone()
flat list [o1, o2, o3]none(3,)
column [[o1], [o2], [o3]]none(3, 1)
row [[o1, o2, o3]]none(1, 3)
one observable(4, 1)(4,)
a (4, 1) column(1, 6)(4, 6)

A SparsePauliOp is a SINGLE element however many terms it holds: one number, the coefficient-weighted sum. Commuting observables share a measurement only within the SAME PUB; two PUBs mean two measurement bases.

The PrimitiveResult holds one PubResult per PUB. result[0].data is a DataBin with exactly evs and stds, both real float64; metadata holds only target_precision and circuit_metadata.

Sources: primitive-input-output · get-started-with-estimator

Observables, layout alignment, and precision​

An n-qubit observable is a weighted sum of Pauli tensor products, which SparsePauliOp stores. Only I and Z are diagonal in the measurement basis; the Estimator automatically changes basis for X (H) and Y (S-dagger then H).

ConstructorTakesWatch for
SparsePauliOp(['ZZ', 'XX'], coeffs=[1.0, 0.5])labels plus coeffs, width inferred from the labelsthere is no num_qubits= keyword here
SparsePauliOp.from_list([('ZZ', 1.0), ('XX', 0.5)])(label, coefficient) pairs on FULL-WIDTH labelsmixing widths raises ValueError: The Nth Pauli is defined over k qubits
SparsePauliOp.from_sparse_list([('Z', [0], 1.0)], num_qubits=3)(label, qubit_indices, coefficient) triples plus num_qubitsnum_qubits is REQUIRED here, and the result is padded with identities to 'IIZ'

Transpiling pads the circuit to full backend width and may permute qubits; the observable does not follow, so align it: isa_observable = observable.apply_layout(isa_circuit.layout). measure_all(), a string observable or initial_layout=[0, 1] do not help — the circuit stays at full backend width.

precision is a target STANDARD ERROR, not a shot count, hence a float. Statistical error falls as 1/sqrt(N), so shot cost goes as 1/precision^2.

RankWhere the precision comes from
1a precision inside the PUB
2run(precision=...)
3twirling being on by default, num_randomizations × shots_per_randomization
4default_shots
5default_precision, documented as 0.015625 = 1/sqrt(4096)

The requested precision is not GUARANTEED, and lower precision costs more QPU time.

Sources: specify-observables-pauli · estimator-options · primitive-input-output

Must know:

  • A 2-qubit observable against a 5-qubit ISA circuit raises ValueError: The number of qubits of the circuit (5) does not match the number of qubits of the ()-th observable (2); only obs.apply_layout(isa.layout) fixes it. — ⚙️ proven in s6-q013
  • SparsePauliOp('ZZ').apply_layout([2, 0], num_qubits=4) returns 'IZIZ': label characters pair with the index list left-to-right, while the printed label stays little-endian. — ⚙️ proven in s6-q033
  • A multi-term SparsePauliOp is ONE observable: from_list([('X', 3.0)]) on |+⟩ returns a 0-d float64 array holding 3.0 — not a length-1 array, not one value per term. — ⚙️ proven in s6-q022
  • Halving the target precision quadruples the shots: measured standard error falls from 0.0195 at 2500 shots to 0.0104 at 4x the shots; 2x only reaches 0.0137. — ⚙️ proven in s6-q017
  • On a shot-based Estimator, precision p buys ceil(1/p²) shots (precision=0.05 reports metadata['shots'] == 400), and a term's std scales as sqrt(1 − ⟨P⟩²)/sqrt(shots), so on an eigenstate of the observable stds is exactly 0.0. — ⚙️ proven in s6-q045

Traps:

  • result[0].data.evs holds the estimates, result[0].data.stds their standard errors; data.expectation_values is an AttributeError, metadata['evs'] a KeyError. — ⚙️ proven in s6-q010
  • Even a single PUB must be wrapped in a list: est.run([(isa, iobs)]). A bare circuit raises ValueError: An invalid Estimator pub-like was given; the V1 run(circuits=..., observables=...) raises TypeError. — ⚙️ proven in s6-q012
  • The reference StatevectorEstimator reports stds of exactly 0.0 — exact calculation, no sampling uncertainty — even when run() asks for a tighter precision. — ⚙️ proven in s6-q037
  • Sweeping ry(theta) over 0 to 2π against SparsePauliOp('Z') traces ONE full cosine period; cos(theta/2) gives half a period, cos(2*theta) two. — ⚙️ proven in s6-q040
  • A dict observable becomes ONE weighted observable: [{'ZI': 2.0, 'IX': 1.0}, 'XZ'] gives evs of shape (2,), the first entry 2⟨ZI⟩ + ⟨IX⟩ — never one value per dict key. — ⚙️ proven in s6-q049
Exam checklist
  • Recite the levels: 0 no mitigation, 1 (default) TREX, 2 adds ZNE and gate twirling.
  • Expect a higher resilience level to cost more processing time and QPU usage, never less.
  • Remember options you set apply in addition to the level's base set.
  • Put each option at its depth — resilience.zne.noise_factors configures ZNE, resilience.zne_mitigation = True enables it.
  • Order an Estimator PUB: circuit, observables, parameter values, precision — wrapped in a list.
  • Predict evs.shape by broadcasting observables against parameter values; a SparsePauliOp is one element.
  • Map every observable through obs.apply_layout(isa.layout) before running on a transpiled circuit.
  • Treat precision as a target standard error: halving it quadruples the shots.

Every fact above is sourced: 📖 links go to official documentation, ⚙️ marks facts observed by executing code against the pinned Qiskit stack (the linked section page shows the proof evidence on its practice questions).