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.
| Level | What it applies | Cost |
|---|---|---|
| 0 | no mitigation at all | none |
| 1 (the default) | readout mitigation via Twirled Readout Error eXtinction (TREX): measurement twirling plus an inversion of the diagonalized readout-error transfer matrix | minimal |
| 2 | everything level 1 does, PLUS zero-noise extrapolation and gate twirling | medium; 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:
| Family | When it acts | Examples | How you switch it on |
|---|---|---|---|
| error suppression | before or during execution, to stop errors happening | dynamical decoupling pulses idling qubits so their idle-time errors approximately cancel | options.dynamical_decoupling.enable |
| error mitigation | after execution, post-processing an ensemble of circuits to remove bias from expectation values | TREX, ZNE, PEA, PEC | resilience_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.
| Level | Fields |
|---|---|
| top | max_execution_time, environment, simulator, default_precision, default_shots, resilience_level, seed_estimator, dynamical_decoupling, resilience, execution, twirling, experimental |
resilience | measure_mitigation, measure_noise_learning, zne_mitigation, zne, pec_mitigation, pec, layer_noise_learning, layer_noise_model |
resilience.zne | amplifier, 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.
| Technique | Turned on only by |
|---|---|
| ZNE | resilience.zne_mitigation = True (or resilience_level = 2) |
| TREX | resilience.measure_mitigation = True |
| PEC | resilience.pec_mitigation = True |
Documented defaults ("what if I touch nothing"):
| Option | Default |
|---|---|
resilience_level | 1 |
resilience.measure_mitigation | True |
resilience.zne_mitigation, resilience.pec_mitigation | False |
resilience.zne.noise_factors | (1, 3, 5) |
resilience.zne.amplifier | gate_folding |
resilience.zne.extrapolator | (exponential, linear) |
dynamical_decoupling.enable, sequence_type | False, XX |
twirling.enable_gates, twirling.enable_measure | False, True |
default_precision | 0.015625 — exactly 1/sqrt(4096) |
max_execution_time | 10800 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_mitigationwithzne_mitigationraises a validation error. — 📖 estimator-options default_precisionsets error-bar length: 0.1 givesstdsaround 0.04–0.10 on a fake backend, 0.01 around 0.002–0.009.options.execution.shotsis no route —executionholds onlyinit_qubitsandrep_delay. — ⚙️ proven ins6-q041
Traps:
resilience_levelvalues3,4,5,-1and1.5all raise a pydanticValidationErrorabout the 0..2 range. — ⚙️ proven ins6-q014- ZNE scale factors live at
options.resilience.zne.noise_factors;zne.factors(wrong name) andresilience.noise_factors(wrong level) areno_such_attributeerrors. — ⚙️ proven ins6-q020 default_precisionmust be strictly positive:0raisesValidationErrorwhether assigned or passed asEstimatorOptions(default_precision=0). — ⚙️ proven ins6-q031EstimatorV2.run()takes onlyprecision=;shots=,default_precision=andresilience_level=raiseTypeError: unexpected keyword argument, andoptions.precisionaValidationError. — ⚙️ proven ins6-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.
| Slot | Contents | Note |
|---|---|---|
| 1 | one QuantumCircuit | may contain Parameter objects |
| 2 | an array of one or more observables | anything ObservablesArrayLike: Pauli, SparsePauliOp, PauliList or a bare string |
| 3 | parameter values to bind | its LAST index runs over the circuit's parameters; omit it or pass None when the circuit has none |
| 4 | a 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.
| Observables | Parameter values | evs.shape |
|---|---|---|
| one observable | none | () |
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).
| Constructor | Takes | Watch for |
|---|---|---|
SparsePauliOp(['ZZ', 'XX'], coeffs=[1.0, 0.5]) | labels plus coeffs, width inferred from the labels | there is no num_qubits= keyword here |
SparsePauliOp.from_list([('ZZ', 1.0), ('XX', 0.5)]) | (label, coefficient) pairs on FULL-WIDTH labels | mixing 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_qubits | num_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.
| Rank | Where the precision comes from |
|---|---|
| 1 | a precision inside the PUB |
| 2 | run(precision=...) |
| 3 | twirling being on by default, num_randomizations × shots_per_randomization |
| 4 | default_shots |
| 5 | default_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); onlyobs.apply_layout(isa.layout)fixes it. — ⚙️ proven ins6-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 ins6-q033- A multi-term
SparsePauliOpis ONE observable:from_list([('X', 3.0)])on |+⟩ returns a 0-dfloat64array holding 3.0 — not a length-1 array, not one value per term. — ⚙️ proven ins6-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.05reportsmetadata['shots'] == 400), and a term's std scales as sqrt(1 − ⟨P⟩²)/sqrt(shots), so on an eigenstate of the observablestdsis exactly 0.0. — ⚙️ proven ins6-q045
Traps:
result[0].data.evsholds the estimates,result[0].data.stdstheir standard errors;data.expectation_valuesis anAttributeError,metadata['evs']aKeyError. — ⚙️ proven ins6-q010- Even a single PUB must be wrapped in a list:
est.run([(isa, iobs)]). A bare circuit raisesValueError: An invalid Estimator pub-like was given; the V1run(circuits=..., observables=...)raisesTypeError. — ⚙️ proven ins6-q012 - The reference
StatevectorEstimatorreportsstdsof exactly0.0— exact calculation, no sampling uncertainty — even whenrun()asks for a tighter precision. — ⚙️ proven ins6-q037 - Sweeping
ry(theta)over 0 to 2π againstSparsePauliOp('Z')traces ONE full cosine period; cos(theta/2) gives half a period, cos(2*theta) two. — ⚙️ proven ins6-q040 - A dict observable becomes ONE weighted observable:
[{'ZI': 2.0, 'IX': 1.0}, 'XZ']givesevsof shape(2,), the first entry 2⟨ZI⟩ + ⟨IX⟩ — never one value per dict key. — ⚙️ proven ins6-q049
- 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_factorsconfigures ZNE,resilience.zne_mitigation = Trueenables it. - Order an Estimator PUB: circuit, observables, parameter values, precision — wrapped in a list.
- Predict
evs.shapeby broadcasting observables against parameter values; aSparsePauliOpis one element. - Map every observable through
obs.apply_layout(isa.layout)before running on a transpiled circuit. - Treat
precisionas 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).