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
| Mode | How you open it | Use it when |
|---|---|---|
| job | SamplerV2(mode=backend), no context manager | you are submitting a single primitive request |
| batch | with 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 |
| session | with 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.
| Timer | What it measures | Configurable? |
|---|---|---|
| maximum TTL | from the first running job until the workload ends | yes, with max_time; the defaults are 8 hours on paid plans and 10 minutes on the Open Plan |
| interactive TTL | the idle gap allowed between consecutive jobs | no — 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)raisesTypeError: ... unexpected keyword argument 'backend', andrun(..., mode=...)fails the same way —modeis a CONSTRUCTOR argument only. — ⚙️ proven ins4-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 ins4-q021 modedoes not resolve names:SamplerV2(mode="ibm_manila")raisesValueError: mode must be of type Backend, Session, Batch or None. — ⚙️ proven ins4-q032Session.backend()returns the QPU's NAME as a string ('fake_manila'), not a Backend object — so it cannot be fed straight back intomode=. — ⚙️ proven ins4-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)
| Step | What you do | What can go wrong |
|---|---|---|
| map | write the problem as circuits and observables | — |
| optimize | pm.run(qc) for an ISA circuit | non-ISA input raises IBMInputValueError, naming the offending instruction |
| execute | run([isa1, isa2]) — ONE iterable of PUBs, shots keyword-only | you get back a PrimitiveResult holding one PUB result per PUB, in order |
| post-process | read 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.
| Call | Gives 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 ins4-q018 - With
mapped = obs.apply_layout(isa.layout)the same Estimator run returnsevs ≈ 0.878;SparsePauliOp("ZZ", target=...)is aTypeError, not a shortcut. — ⚙️ proven ins4-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 ins4-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 ins4-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 aValueError. — ⚙️ proven ins4-q046 - Zip in the wild: five observables plus a
(5, 1)value array on a ONE-parameter circuit coerce to shapes(5,)and(5,), soevs.shapeis(5,)— with one free parameter the trailing axis is the parameter axis, not a broadcast axis. — ⚙️ proven ins4-q048
Traps:
- Primitives reject non-ISA circuits: an untranspiled
hraisesIBMInputValueError: 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 ins4-q012 - Native basis gates alone are not enough: a
cxbetween the non-adjacent qubits 0 and 4 of a line-topology device raises the sameIBMInputValueError— an ISA circuit must satisfy the coupling map too. — ⚙️ proven ins4-q026 - With no measurement in the circuit,
result[0].data.measraisesAttributeError; callqc.measure_all()BEFORE transpiling. — ⚙️ proven ins4-q023
- 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.layoutwithapply_layoutbefore handing it to the Estimator. - Hand
run()one iterable of PUBs, and stateshotsexplicitly rather than trusting a default. - Add measurements before transpiling, or the Sampler's
DataBincomes 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).