Skip to main content

Cram sheet — Section 4: Run quantum circuits

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.

Demonstrate an understanding of execution modes such as: session with dedicated, priority, and batch mode

Three modes, and the mode= that selects them

Qiskit Runtime schedules work in exactly three ways, and the mode is chosen by an ARGUMENT, never by lexical position.

with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch) # or omit mode= and inherit the context
ModeHow you open itUse it when
jobSamplerV2(mode=backend), no context manageryou are submitting a single primitive request
batchwith Batch(backend=backend):the jobs are independent and all known at the outset: you queue once, their classical pre-processing is threaded in parallel, and the executions are packed tightly, which limits device drift
sessionwith Session(backend=backend):the next circuit depends on the previous result, or PEC/PEA need a once-learned noise model to stay valid — the active window is exclusive, calibration jobs included

The documented default is blunt: use batch unless your inputs are not all ready, use a session for iterative workloads or genuinely dedicated access, and always use job mode for a single request. Sessions are the expensive option, so "wrap it in a session to be safe" is the wrong instinct — and the Open Plan cannot submit session jobs at all. mode= accepts a Backend, a Session or a Batch object; writing mode=backend INSIDE a batch context silently puts you back in job mode.

Sources: execution-modes · choose-execution-mode · run-jobs-session

Two timers govern a session or batch

The maximum TTL starts when the first job begins RUNNING — not when you open the context — and it does not pause.

TimerWhat it measuresConfigurable?
maximum TTLfrom the first running job until the workload endsyes, with max_time; the defaults are 8 hours on paid plans and 10 minutes on the Open Plan
interactive TTLthe idle gap allowed between consecutive jobsno — one minute for batches

When the maximum TTL is reached the workload is terminated: jobs already running continue, and jobs still queued are put into a failed state. Exceeding the interactive TTL is milder — the workload is temporarily deactivated until a new job works its way back through the normal queue.

Closing kills nothing. Calling close(), or simply exiting the with block, moves the workload to "In progress, not accepting new jobs": everything already submitted runs to completion and the results stay retrievable.

Sources: max-execution-time · run-jobs-batch · run-jobs-session

Must know:

  • Batched jobs are NOT guaranteed to run in submission order and get no exclusive access — other users' jobs and QPU calibration jobs can interleave. — 📖 execution-modes
  • Queuing time does not decrease for the FIRST job submitted within a batch or a session, so neither mode benefits a single-job workload. — 📖 execution-modes

Traps:

  • The V1 spelling is gone: SamplerV2(backend=backend) raises TypeError: ... unexpected keyword argument 'backend', and run(..., mode=...) fails the same way — mode is a CONSTRUCTOR argument only. — ⚙️ proven in s4-q016
  • A primitive with no mode and no enclosing context raises ValueError: A backend or session must be specified. in the CONSTRUCTOR — run() is never reached. — ⚙️ proven in s4-q021
  • mode does not resolve names: SamplerV2(mode="ibm_manila") raises ValueError: mode must be of type Backend, Session, Batch or None. — ⚙️ proven in s4-q032
  • Session.backend() returns the QPU's NAME as a string ('fake_manila'), not a Backend object — so it cannot be fed straight back into mode=. — ⚙️ proven in s4-q042

Demonstrate understanding of how to run quantum circuits with real hardware using Qiskit Runtime primitives and applying broadcasting rules

ISA in, PUB results out

V2 primitives accept ISA circuits ONLY — every instruction native to the backend's target, every two-qubit gate on a connected pair — and they never transpile for you.

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa = pm.run(qc)
result = SamplerV2(mode=backend).run([isa], shots=1024) # run(pubs, *, shots=None)
StepWhat you doWhat can go wrong
mapwrite the problem as circuits and observables
optimizepm.run(qc) for an ISA circuitnon-ISA input raises IBMInputValueError, naming the offending instruction
executerun([isa1, isa2]) — ONE iterable of PUBs, shots keyword-onlyyou get back a PrimitiveResult holding one PUB result per PUB, in order
post-processread result[0].data.meas (Sampler) or result[0].data.evs (Estimator)a circuit with no measurement yields a DataBin with no fields

Transpiling also widens the circuit to the device's qubit count, and that is what breaks the Estimator half of the workflow: the observable must be mapped through the same layout with obs.apply_layout(isa.layout), because nothing pads a narrow observable for you. Measurements, by contrast, survive transpilation untouched.

Sources: transpile · get-started-with-sampler · primitive-input-output

Credentials, backend choice, rehearsing without a QPU

A saved account is what lets QiskitRuntimeService() work with no arguments.

QiskitRuntimeService.save_account(token="<api-key>", instance="<CRN>",
name="me", set_as_default=True)
service = QiskitRuntimeService() # channel defaults to ibm_quantum_platform

The parameter is token, not api_key, instance is a Cloud Resource Name, and the file written is $HOME/.qiskit/qiskit-ibm.json — do this only in a trusted environment. With several saved accounts, none marked default and none named at initialization, the account whose name comes LAST alphabetically is used.

CallGives you
service.least_busy(operational=True, simulator=False)the operational real QPU with the shortest queue; min_num_qubits and a filters callable are also accepted
service.backends()a list that is NOT queue-sorted
service.backend("name")one named backend — the name is positional
a qiskit_ibm_runtime.fake_provider backend as mode=a snapshot of a real QPU, with its coupling map, basis gates and noise model
an Aer simulator as mode=local execution; QiskitRuntimeService(channel="local") does the same at service level

Local testing mode promises that only the backend changes when you move to hardware. Two local quirks: session.details() and session.session_id come back None, and a fake backend falls through to the local 1024-shot default where the Runtime service uses 4096 — so state shots explicitly.

Sources: initialize-account · save-credentials · local-testing-mode · get-started-with-sampler

Must know:

  • Transpiling widens the circuit to the device, so an unmapped observable breaks: ValueError: The number of qubits of the circuit (5) does not match the number of qubits of the ()-th observable (2). — ⚙️ proven in s4-q018
  • With mapped = obs.apply_layout(isa.layout) the same Estimator run returns evs ≈ 0.878; SparsePauliOp("ZZ", target=...) is a TypeError, not a shortcut. — ⚙️ proven in s4-q036
  • Device noise trims the signal rather than destroying it: a Bell state's ⟨ZZ⟩ through the Estimator on a noisy fake backend comes back at 0.874 — close to +1, never above it. — ⚙️ proven in s4-q044
  • The guide names four broadcasting patterns: broadcast single observable (observables shape (), reused at every parameter value), zip (identically shaped arrays matched element by element), outer/product ((1, 4) parameters with (3, 1) observables give (3, 4)), and standard nd generalization (an extra leading axis on the observables). — ⚙️ proven in s4-q045
  • Zip is the one pattern that does not multiply the work: (3,) parameters with (3,) observables give three estimates. Its look-alikes: one observable over the same parameters is broadcast, (3, 1) against (3,) is a product, and (3,) against (4,) is a ValueError. — ⚙️ proven in s4-q046
  • Zip in the wild: five observables plus a (5, 1) value array on a ONE-parameter circuit coerce to shapes (5,) and (5,), so evs.shape is (5,) — with one free parameter the trailing axis is the parameter axis, not a broadcast axis. — ⚙️ proven in s4-q048

Traps:

  • Primitives reject non-ISA circuits: an untranspiled h raises IBMInputValueError: The instruction h on qubits (0,) is not supported by the target system — and a fake backend enforces this exactly like hardware, so local testing mode is no escape. — ⚙️ proven in s4-q012
  • Native basis gates alone are not enough: a cx between the non-adjacent qubits 0 and 4 of a line-topology device raises the same IBMInputValueError — an ISA circuit must satisfy the coupling map too. — ⚙️ proven in s4-q026
  • With no measurement in the circuit, result[0].data.meas raises AttributeError; call qc.measure_all() BEFORE transpiling. — ⚙️ proven in s4-q023
Exam checklist
  • Pick job mode for a single request, batch for independent jobs, session for iterative work.
  • Pass the execution target as mode= — a Backend, Session or Batch object, never a name string.
  • Transpile with a preset pass manager before every primitive run; V2 primitives reject non-ISA circuits.
  • Map every observable through isa.layout with apply_layout before handing it to the Estimator.
  • Hand run() one iterable of PUBs, and state shots explicitly rather than trusting a default.
  • Add measurements before transpiling, or the Sampler's DataBin comes back with no fields.
  • Remember the maximum TTL starts at the first RUNNING job, not when you open the context.
  • Rehearse on a fake backend or Aer, then change only the backend to reach hardware.

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).