Cram sheet — Section 5: Use the sampler 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 sampler primitive options such as dynamical decoupling
The SamplerOptions tree — and the knob that is not in it
SamplerOptions is a validated pydantic dataclass, so every field name is checked — and SamplerV2 has no resilience_level. That knob is EstimatorV2's expectation-value mitigation (TREX at level 1, ZNE at level 2). The blueprint's wording, "set sampler primitive options such as resilience levels", is the bait.
| Level | Fields |
|---|---|
| top | max_execution_time, environment, simulator, default_shots, dynamical_decoupling, execution, twirling, experimental |
dynamical_decoupling | enable, sequence_type (XX default, XpXm, XY4), extra_slack_distribution, scheduling_method, skip_reset_qubits |
twirling | enable_gates, enable_measure, num_randomizations, shots_per_randomization, strategy |
execution | init_qubits, rep_delay, meas_type — no shots, no seed_simulator |
simulator | noise_model, seed_simulator, coupling_map, basis_gates |
Learn each knob's DEPTH, not just its name. Misspelled names are rejected; experimental is an opt-in dict you fill yourself, not a catch-all for unknown names.
Sources: sampler-options · options-sampler-options
Setting options, and where the shot count comes from
Untouched options stay Unset and take the server default, so behaviour does not drift with the client version.
sampler = SamplerV2(mode=backend, options={"default_shots": 4096})
sampler.options.dynamical_decoupling.enable = True
sampler.options.update(default_shots=1024,
dynamical_decoupling={"sequence_type": "XpXm"})
All three forms are valid: a mapping or SamplerOptions object in the constructor, attribute assignment, or a bulk update(); the options object is not read-only. The V1 sampler.set_options(...) does NOT exist.
The documented shots precedence:
| Rank | Where the shot count comes from |
|---|---|
| 1 | the shot count carried in the PUB's third slot |
| 2 | the shots= keyword on run() |
| 3 | with twirling on, num_randomizations × shots_per_randomization |
| 4 | options.default_shots |
shots must be an integer; a one-element list raises TypeError. A local fake backend falls through to 1024; the Runtime service's default is 4096.
Sources: runtime-options-overview · sampler-options
Must know:
- Settable on SamplerV2:
options.default_shots,options.dynamical_decoupling.enable,options.twirling.enable_gates;resilience_levelandoptimization_levelboth fail validation. — ⚙️ proven ins5-q020 - A PUB shot count beats the option: with
options.default_shots = 1000, the PUB(isa, None, 50)runs exactly 50 shots. — ⚙️ proven ins5-q026
Traps:
sampler.options.resilience_level = 1raises a pydanticValidationError("Object has no attribute 'resilience_level'"); the constructor'soptions={...}mapping rejects it too. — ⚙️ proven ins5-q014- Configuring is not enabling: dynamical decoupling needs
options.dynamical_decoupling.enable = True; setting onlysequence_typeis accepted but leavesenableUnset. — ⚙️ proven ins5-q019 - The simulator seed lives at
options.simulator.seed_simulator;options.execution.seed_simulator, top-levelseed_simulator,run(seed=42)andrun(options={...})are all rejected. — ⚙️ proven ins5-q031 SamplerV2(mode=backend, options={"default_shots": 4096})is the constructor form; a"shots"key is rejected, andSamplerV2(shots=...)or(default_shots=...)is aTypeError. — ⚙️ proven ins5-q033twirling_levelandreadoutare nottwirlingfields; those near-miss names fail validation. — ⚙️ proven ins5-q027
Bypass runtime error mitigations and implement your own
Nothing to switch off — so roll your own
The Sampler is the low-level primitive: it returns the sampled bitstrings shot by shot, with no readout correction. There is no Sampler mitigation to "bypass" by switching it off.
| Technique | Where it lives | What it does |
|---|---|---|
| dynamical decoupling | Sampler, options.dynamical_decoupling | suppression: pulse sequences cancel coherent errors on idling qubits |
| Pauli twirling of gates and/or measurements | Sampler, options.twirling | suppression: randomizes over an ensemble of equivalent circuits so arbitrary noise becomes Pauli noise |
| TREX, zero-noise extrapolation, PEC, error amplification | Estimator only, options.resilience.* or resilience_level | mitigation: corrects expectation values |
Neither suppression option rewrites counts into "mitigated" values, and mitigation has no Sampler counterpart: there is no single number to rescale, only a histogram. Code the correction yourself in post-processing.
| Scheme | How it works | Trade-off |
|---|---|---|
| post-selection | drop the shots that violate a known symmetry of the ideal output — a Bell circuit can only produce 00 or 11 — then renormalize over what survives | cheap and needs no calibration, but only works when the ideal support is known, and it throws information away |
| assignment-matrix (confusion-matrix) inversion | prepare each computational basis state with calibration circuits, record what you read back, assemble the matrix mapping true outcomes to observed ones, and invert it (or least-squares invert it) | rigorous; this is what packages such as M3/mthree automate, and the raw per-shot record is exactly their input |
Post-process the BitArray: faster than round-tripping through get_counts().
Sources: sampler-noise-management · error-mitigation-and-suppression-techniques · primitive-input-output
Must know:
- Post-selection is code you write: a Bell run's raw counts
{'00': 1941, '11': 1822, '01': 90, '10': 147}become{'00': 1941, '11': 1822}by dropping odd-parity shots and renormalizing. — ⚙️ proven ins5-q028 - A
BitArraysupports boolean-mask post-selection on another register,slice_bits,slice_shots,expectation_valuesfor diagonal observables, andconcatenate_bitsto merge registers. — 📖 primitive-input-output
Traps:
BitArray.postselect(indices, selection)takes BIT indices (0 is the rightmost character, as inslice_bits) andselectionlists values to KEEP: on000/111/100/011,postselect([2], [0])keeps only000and011, still 3 bits. — ⚙️ proven ins5-q045
Understand the theoretical background behind the sampler primitive
PUBs, shots, and broadcasting
The Sampler runs a circuit many times, recording the measured register each shot — draws from the circuit's distribution over computational-basis states. It never returns amplitudes or expectation values; probabilities are frequencies you compute, with statistical error shrinking like 1/√N_shots, so halving the error costs four times the shots.
Input is a PUB (Primitive Unified Bloc): a tuple of at most three entries, (circuit, parameter_values, shots), the last two optional. The circuit must contain measurements; run() takes a LIST of PUBs.
result = sampler.run([(isa, params, 200)]) # never run(isa)
# (isa, 256, vals) -> TypeError: shots must be an integer
# (isa, vals, 200, 1) -> ValueError: The length of pub must be 1, 2 or 3
Binding follows NumPy broadcasting plus one rule: the parameter array's LAST axis holds the circuit's free parameters and is consumed; the shape before it becomes the result's shape.
| Parameter array (circuit has 2 parameters) | data.meas.shape |
|---|---|
(6, 2) | (6,) |
(3, 4, 2) | (3, 4), indexable as data.meas[i, j] |
(5, 3) | ValueError: Length of ('p[0]', 'p[1]') inconsistent with last dimension |
Transposing, flattening or splitting into one PUB per row all raise that same error; reshaping to (5, 2) is the fix.
Sources: primitive-input-output · primitives · multi-product-formula
Reading a SamplerPubResult
run() returns a PrimitiveResult with one entry per PUB, in submission order.
| Access path | What you get |
|---|---|
len(result) | the number of PUBs submitted — three circuits in one run() give three PUB results |
result[0].data | a DataBin holding one BitArray per classical register, named after the register (data.alice, data.bob) |
.get_counts() | the histogram, as a plain dict |
.get_int_counts() | the same histogram with the bitstrings decoded as integers ('11' becomes the key 3) |
.get_bitstrings() | the raw per-shot list, in the order the shots were taken |
.get_counts(0) on a broadcast result | that one parameter set's 500 shots; with no index, get_counts() POOLS every set (4 × 500 = 2000) |
Only measure_all() creates a register called meas. The per-shot view lets you correlate outcomes across registers before aggregating; a counts dict cannot.
Sources: primitive-input-output
Must know:
- Shots are per parameter set, not per PUB: 6 parameter values with
shots=200make 6 × 200 = 1200 circuit executions. — ⚙️ proven ins5-q035 get_counts()values sum to the shot count (shots=1000→ 1000) however many distinct bitstrings appear. — ⚙️ proven ins5-q010- Bitstrings are little-endian: qubit 0 is the LAST character, so read
b[-1], notb[0]; afterx(0)on two qubits the dominant outcome is'01'. — ⚙️ proven ins5-q032 - Runtime
SamplerV2rejects a non-ISA circuit withIBMInputValueError; the fix is transpiling with a preset pass manager for the backend, not changing the run call. — ⚙️ proven ins5-q013 - The reference
StatevectorSamplerinqiskit.primitivesaccepts abstract (non-ISA) instructions, since it simulates a local statevector — the ISA requirement is a Runtime-primitive rule. — 📖 simulate-with-qiskit-sdk-primitives join_data()puts the FIRST-declared register at the low-index, rightmost end:out(reads10) declared beforeanc(reads1) joins to'110';join_data(['anc', 'out'])gives'101'. — ⚙️ proven ins5-q044
Traps:
result[0].data.measis aBitArrayof per-shot outcomes, not a counts dict;get_counts()gives the summary as a plaindict. — ⚙️ proven ins5-q011QuantumCircuit(2, 2)names its registerc, so outcomes are indata.c;measexists only aftermeasure_all(). — ⚙️ proven ins5-q025get_counts()lists only outcomes that occurred: a 40-shot two-qubit run that never produced10gives a three-key dict, soplot_histogramdraws three bars — no zero-height bar for the missing outcome. — ⚙️ proven ins5-q041- A fake backend is not an exact simulator: a seeded 2000-shot
h(0); cx(0,1); x(2)run on FakeManilaV2 hits all eight outcomes, the ideal peaks100and111holding about 43% and 40% of shots. — ⚙️ proven ins5-q040 StatevectorSamplerrejects dynamic circuits: anif_testraisesQiskitError: 'StatevectorSampler cannot handle ControlFlowOp', a reset or gate after a measurement'... cannot handle mid-circuit measurements'. UseSamplerV2(mode=AerSimulator())instead. — ⚙️ proven ins5-q052
- Place each SamplerV2 knob at its level (
dynamical_decoupling,twirling,execution,simulator); SamplerV2 has noresilience_level. - Enable a technique explicitly; setting only
sequence_typeleavesenableUnset. - Recite the shots precedence: PUB slot,
run(shots=), twirling randomizations,options.default_shots. - Hand
run()a LIST of PUBs, each at most(circuit, parameter_values, shots), circuits measured. - Derive the result shape: the parameter array's last axis binds parameters, leading axes survive.
- Read outcomes from the register-named
BitArray;data.measexists only aftermeasure_all(). - Read every bitstring right-to-left — qubit 0 is the last character.
- Build readout correction yourself (post-selection or assignment-matrix inversion); the Sampler ships none.
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).