Skill Tracer
Skills › Scientific Agent Skills › qiskit › patterns.md

patterns.md

389 lines · skills/qiskit/references/patterns.md at 5f2bc03

Open on GitHub
1# End-to-End Qiskit Patterns
2
3Cloud authentication, QPU, session, and batch snippets are illustrative and were not executed in this refresh. Local evidence is listed in [sources.md](sources.md).
4Use a four-stage workflow:
5
6```text
7Map -> Optimize -> Execute -> Analyze
8```
9
10Keep these stages separate so circuit construction, compilation, paid execution, and interpretation can each be tested and reproduced.

End-to-End Qiskit Patterns

Cloud authentication, QPU, session, and batch snippets are illustrative and were not executed in this refresh. Local evidence is listed in sources.md. Use a four-stage workflow:

Map -> Optimize -> Execute -> Analyze

Keep these stages separate so circuit construction, compilation, paid execution, and interpretation can each be tested and reproduced.

Raw lines 1 to 11
12## Pattern 1: Local Bell-State Baseline

Pattern 1: Local Bell-State Baseline

Raw lines 12 to 13
14### Map
16```python
17from qiskit import QuantumCircuit
19circuit = QuantumCircuit(2)
20circuit.h(0)
21circuit.cx(0, 1)
22circuit.measure_all()
23```

Map

from qiskit import QuantumCircuit

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
Raw lines 14 to 24
25### Optimize
27Local statevector primitives accept abstract circuits. A pass manager is still useful for testing the same workflow shape:
29```python
30from qiskit.transpiler import generate_preset_pass_manager
32pass_manager = generate_preset_pass_manager(
33 optimization_level=1,
34 seed_transpiler=31,
36compiled = pass_manager.run(circuit)
37```

Optimize

Local statevector primitives accept abstract circuits. A pass manager is still useful for testing the same workflow shape:

from qiskit.transpiler import generate_preset_pass_manager

pass_manager = generate_preset_pass_manager(
    optimization_level=1,
    seed_transpiler=31,
)
compiled = pass_manager.run(circuit)
Raw lines 25 to 38
39### Execute
41```python
42from qiskit.primitives import StatevectorSampler
44sampler = StatevectorSampler(seed=31)
45pub_result = sampler.run([compiled], shots=2048).result()[0]
46```

Execute

from qiskit.primitives import StatevectorSampler

sampler = StatevectorSampler(seed=31)
pub_result = sampler.run([compiled], shots=2048).result()[0]
Raw lines 39 to 47
48### Analyze
50```python
51counts = pub_result.data.meas.get_counts()
52total = sum(counts.values())
53probabilities = {
54 bitstring: count / total
55 for bitstring, count in counts.items()
58unexpected = sum(
59 probability
60 for state, probability in probabilities.items()
61 if state not in {"00", "11"}
63print(probabilities, unexpected)
64```
66Use this ideal baseline before adding a noise model or QPU.

Analyze

counts = pub_result.data.meas.get_counts()
total = sum(counts.values())
probabilities = {
    bitstring: count / total
    for bitstring, count in counts.items()
}

unexpected = sum(
    probability
    for state, probability in probabilities.items()
    if state not in {"00", "11"}
)
print(probabilities, unexpected)

Use this ideal baseline before adding a noise model or QPU.

Raw lines 48 to 67
68## Pattern 2: IBM QPU Sampling

Pattern 2: IBM QPU Sampling

Raw lines 68 to 69
70### Map
72```python
73from qiskit import QuantumCircuit
75circuit = QuantumCircuit(3)
76circuit.h(0)
77circuit.cx(0, 1)
78circuit.cx(1, 2)
79circuit.measure_all()
80```

Map

from qiskit import QuantumCircuit

circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.measure_all()
Raw lines 70 to 81
82### Select and Optimize
84```python
85from qiskit.transpiler import generate_preset_pass_manager
86from qiskit_ibm_runtime import QiskitRuntimeService
88service = QiskitRuntimeService()
89backend = service.least_busy(
90 operational=True,
91 simulator=False,
92 min_num_qubits=circuit.num_qubits,
95pass_manager = generate_preset_pass_manager(
96 backend=backend,
97 optimization_level=1,
98 seed_transpiler=31,
100isa_circuit = pass_manager.run(circuit)
102print("depth:", isa_circuit.depth())
103print("operations:", isa_circuit.count_ops())
104print("layout:", isa_circuit.layout)
105```

Select and Optimize

from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True,
    simulator=False,
    min_num_qubits=circuit.num_qubits,
)

pass_manager = generate_preset_pass_manager(
    backend=backend,
    optimization_level=1,
    seed_transpiler=31,
)
isa_circuit = pass_manager.run(circuit)

print("depth:", isa_circuit.depth())
print("operations:", isa_circuit.count_ops())
print("layout:", isa_circuit.layout)
Raw lines 82 to 106
107### Execute
109```python
110from qiskit_ibm_runtime.executor_sampler import Sampler
112sampler = Sampler(mode=backend)
113job = sampler.run([isa_circuit], shots=4096)
114job_id = job.job_id()
115print("job_id:", job_id)
116pub_result = job.result()[0]
117```

Execute

from qiskit_ibm_runtime.executor_sampler import Sampler

sampler = Sampler(mode=backend)
job = sampler.run([isa_circuit], shots=4096)
job_id = job.job_id()
print("job_id:", job_id)
pub_result = job.result()[0]
Raw lines 107 to 118
119### Analyze
121```python
122counts = pub_result.data.meas.get_counts()
123shots = sum(counts.values())
124ghz_support = (
125 counts.get("000", 0) + counts.get("111", 0)
126) / shots
128record = {
129 "job_id": job_id,
130 "backend": backend.name,
131 "shots": shots,
132 "ghz_support": ghz_support,
133 "result_metadata": pub_result.metadata,
135```
137Do not present GHZ support as state fidelity without a justified measurement protocol.

Analyze

counts = pub_result.data.meas.get_counts()
shots = sum(counts.values())
ghz_support = (
    counts.get("000", 0) + counts.get("111", 0)
) / shots

record = {
    "job_id": job_id,
    "backend": backend.name,
    "shots": shots,
    "ghz_support": ghz_support,
    "result_metadata": pub_result.metadata,
}

Do not present GHZ support as state fidelity without a justified measurement protocol.

Raw lines 119 to 138
139## Pattern 3: Parameter Sweep with One Compilation
141Build and compile one parameterized circuit:
143```python
144import numpy as np
145from qiskit import QuantumCircuit
146from qiskit.circuit import Parameter
147from qiskit.transpiler import generate_preset_pass_manager
149theta = Parameter("theta")
150circuit = QuantumCircuit(2)
151circuit.ry(theta, 0)
152circuit.cx(0, 1)
153circuit.measure_all()
155pass_manager = generate_preset_pass_manager(
156 backend=backend,
157 optimization_level=1,
158 seed_transpiler=31,
160isa_circuit = pass_manager.run(circuit)
162parameter_values = np.linspace(0, np.pi, 21).reshape(-1, 1)
163```
165Submit values through one PUB:
167```python
168sampler = Sampler(mode=backend)
169pub_result = sampler.run(
170 [(isa_circuit, parameter_values)],
171 shots=1024,
172).result()[0]
174counts_by_point = [
175 pub_result.data.meas.get_counts(index)
176 for index in range(len(parameter_values))
178```
180This preserves a consistent layout and avoids repeated compilation.

Pattern 3: Parameter Sweep with One Compilation

Build and compile one parameterized circuit:

import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.transpiler import generate_preset_pass_manager

theta = Parameter("theta")
circuit = QuantumCircuit(2)
circuit.ry(theta, 0)
circuit.cx(0, 1)
circuit.measure_all()

pass_manager = generate_preset_pass_manager(
    backend=backend,
    optimization_level=1,
    seed_transpiler=31,
)
isa_circuit = pass_manager.run(circuit)

parameter_values = np.linspace(0, np.pi, 21).reshape(-1, 1)

Submit values through one PUB:

sampler = Sampler(mode=backend)
pub_result = sampler.run(
    [(isa_circuit, parameter_values)],
    shots=1024,
).result()[0]

counts_by_point = [
    pub_result.data.meas.get_counts(index)
    for index in range(len(parameter_values))
]

This preserves a consistent layout and avoids repeated compilation.

Raw lines 139 to 181
182## Pattern 4: Variational Estimator Loop
184Use a parameterized ansatz and map the observable after compilation:
186```python
187import numpy as np
188from qiskit.circuit.library import efficient_su2
189from qiskit.quantum_info import SparsePauliOp
190from qiskit.transpiler import generate_preset_pass_manager
192ansatz = efficient_su2(
193 num_qubits=2,
194 reps=1,
195 entanglement="linear",
197hamiltonian = SparsePauliOp.from_list(
198 [
199 ("ZI", 1.0),
200 ("IZ", 1.0),
201 ("XX", 0.2),
202 ]
205pass_manager = generate_preset_pass_manager(
206 backend=backend,
207 optimization_level=1,
208 seed_transpiler=31,
210isa_ansatz = pass_manager.run(ansatz)
211isa_hamiltonian = hamiltonian.apply_layout(isa_ansatz.layout)
212initial_point = np.zeros(ansatz.num_parameters)
213```
215Run iterative Estimator calls. Session mode requires an eligible paid plan:
217```python
218from scipy.optimize import minimize
219from qiskit_ibm_runtime import Session
220from qiskit_ibm_runtime.executor_estimator import Estimator
222history = []
224with Session(backend=backend, max_time="20m") as session:
225 estimator = Estimator(
226 mode=session,
227 options={"resilience_level": 1},
228 )
230 def objective(parameters):
231 pub = (
232 isa_ansatz,
233 isa_hamiltonian,
234 [parameters],
235 )
236 pub_result = estimator.run(
237 [pub],
238 precision=0.03,
239 ).result()[0]
240 value = float(np.asarray(pub_result.data.evs).reshape(-1)[0])
241 history.append(
242 {
243 "parameters": parameters.copy(),
244 "value": value,
245 "metadata": pub_result.metadata,
246 }
247 )
248 return value
250 optimum = minimize(
251 objective,
252 initial_point,
253 method="COBYLA",
254 options={"maxiter": 25},
255 )
256```
258For Open Plan access, instantiate `Estimator(mode=backend)` and use job mode. The circuit remains compiled once in either case.

Pattern 4: Variational Estimator Loop

Use a parameterized ansatz and map the observable after compilation:

import numpy as np
from qiskit.circuit.library import efficient_su2
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager

ansatz = efficient_su2(
    num_qubits=2,
    reps=1,
    entanglement="linear",
)
hamiltonian = SparsePauliOp.from_list(
    [
        ("ZI", 1.0),
        ("IZ", 1.0),
        ("XX", 0.2),
    ]
)

pass_manager = generate_preset_pass_manager(
    backend=backend,
    optimization_level=1,
    seed_transpiler=31,
)
isa_ansatz = pass_manager.run(ansatz)
isa_hamiltonian = hamiltonian.apply_layout(isa_ansatz.layout)
initial_point = np.zeros(ansatz.num_parameters)

Run iterative Estimator calls. Session mode requires an eligible paid plan:

from scipy.optimize import minimize
from qiskit_ibm_runtime import Session
from qiskit_ibm_runtime.executor_estimator import Estimator

history = []

with Session(backend=backend, max_time="20m") as session:
    estimator = Estimator(
        mode=session,
        options={"resilience_level": 1},
    )

    def objective(parameters):
        pub = (
            isa_ansatz,
            isa_hamiltonian,
            [parameters],
        )
        pub_result = estimator.run(
            [pub],
            precision=0.03,
        ).result()[0]
        value = float(np.asarray(pub_result.data.evs).reshape(-1)[0])
        history.append(
            {
                "parameters": parameters.copy(),
                "value": value,
                "metadata": pub_result.metadata,
            }
        )
        return value

    optimum = minimize(
        objective,
        initial_point,
        method="COBYLA",
        options={"maxiter": 25},
    )

For Open Plan access, instantiate Estimator(mode=backend) and use job mode. The circuit remains compiled once in either case.

Raw lines 182 to 259
260## Pattern 5: Independent Experiments in a Batch
262Compile all circuits against the same target:
264```python
265pass_manager = generate_preset_pass_manager(
266 backend=backend,
267 optimization_level=1,
268 seed_transpiler=31,
270isa_circuits = pass_manager.run(circuits)
271```
273Submit independent jobs:
275```python
276from qiskit_ibm_runtime import Batch
277from qiskit_ibm_runtime.executor_sampler import Sampler
279with Batch(backend=backend, max_time="10m") as batch:
280 sampler = Sampler(mode=batch)
281 jobs = [
282 sampler.run([circuit], shots=2048)
283 for circuit in isa_circuits
284 ]
286job_records = [
287 {
288 "job_id": job.job_id(),
289 "result": job.result(),
290 }
291 for job in jobs
293```
295Keep the job list aligned with an explicit experiment manifest.

Pattern 5: Independent Experiments in a Batch

Compile all circuits against the same target:

pass_manager = generate_preset_pass_manager(
    backend=backend,
    optimization_level=1,
    seed_transpiler=31,
)
isa_circuits = pass_manager.run(circuits)

Submit independent jobs:

from qiskit_ibm_runtime import Batch
from qiskit_ibm_runtime.executor_sampler import Sampler

with Batch(backend=backend, max_time="10m") as batch:
    sampler = Sampler(mode=batch)
    jobs = [
        sampler.run([circuit], shots=2048)
        for circuit in isa_circuits
    ]

job_records = [
    {
        "job_id": job.job_id(),
        "result": job.result(),
    }
    for job in jobs
]

Keep the job list aligned with an explicit experiment manifest.

Raw lines 260 to 296
297## Pattern 6: Ideal, Noisy, and Hardware Ladder
299Evaluate in three stages:
3011. `StatevectorSampler` or `StatevectorEstimator`.
3022. Aer or a fake backend using a recorded noise/target snapshot.
3033. The selected QPU with the same logical inputs and analysis.
305Do not force every stage to use identical compiled circuits: simulator and QPU targets differ. Preserve the same logical circuit and compile separately for each target.
307Compare:
309- ideal application metric,
310- noisy-model application metric,
311- QPU application metric,
312- compiler layout and native operation counts,
313- uncertainty and metadata.
315Avoid claiming a noise model predicts QPU output merely because the two results are close once.

Pattern 6: Ideal, Noisy, and Hardware Ladder

Evaluate in three stages:

  1. StatevectorSampler or StatevectorEstimator.
  2. Aer or a fake backend using a recorded noise/target snapshot.
  3. The selected QPU with the same logical inputs and analysis.

Do not force every stage to use identical compiled circuits: simulator and QPU targets differ. Preserve the same logical circuit and compile separately for each target.

Compare:

  • ideal application metric,
  • noisy-model application metric,
  • QPU application metric,
  • compiler layout and native operation counts,
  • uncertainty and metadata.

Avoid claiming a noise model predicts QPU output merely because the two results are close once.

Raw lines 297 to 316
317## Pattern 7: Mitigation A/B Test
319Run an unmitigated baseline:
321```python
322from qiskit_ibm_runtime.executor_estimator import Estimator
324baseline_estimator = Estimator(
325 mode=backend,
326 options={"resilience_level": 0},
328baseline = baseline_estimator.run(
329 [(isa_circuit, isa_observable)],
330 precision=0.03,
331).result()[0]
332```
334Run a mitigated configuration:
336```python
337mitigated_estimator = Estimator(
338 mode=backend,
339 options={"resilience_level": 2},
341mitigated = mitigated_estimator.run(
342 [(isa_circuit, isa_observable)],
343 precision=0.03,
344).result()[0]
345```
347Compare both against a justified ideal or classically verifiable reference. Report uncertainty, usage, and total circuit/shot overhead. Do not assume the mitigated value is closer.

Pattern 7: Mitigation A/B Test

Run an unmitigated baseline:

from qiskit_ibm_runtime.executor_estimator import Estimator

baseline_estimator = Estimator(
    mode=backend,
    options={"resilience_level": 0},
)
baseline = baseline_estimator.run(
    [(isa_circuit, isa_observable)],
    precision=0.03,
).result()[0]

Run a mitigated configuration:

mitigated_estimator = Estimator(
    mode=backend,
    options={"resilience_level": 2},
)
mitigated = mitigated_estimator.run(
    [(isa_circuit, isa_observable)],
    precision=0.03,
).result()[0]

Compare both against a justified ideal or classically verifiable reference. Report uncertainty, usage, and total circuit/shot overhead. Do not assume the mitigated value is closer.

Raw lines 317 to 348
349## Experiment Manifest
351Persist enough information to reconstruct the workflow:
353```python
354from importlib.metadata import version
356manifest = {
357 "packages": {
358 "qiskit": version("qiskit"),
359 "qiskit-ibm-runtime": version("qiskit-ibm-runtime"),
360 },
361 "backend": backend.name,
362 "job_ids": job_ids,
363 "seed_transpiler": 31,
364 "optimization_level": 1,
365 "shots": 4096,
366 "primitive_options": resolved_options,
367 "logical_parameter_order": [
368 parameter.name for parameter in logical_circuit.parameters
369 ],
370 "compiled_layout": str(isa_circuit.layout),
371 "compiled_operations": dict(isa_circuit.count_ops()),
373```
375Store logical and ISA circuits in QPY, and store the manifest in a text format such as JSON after converting Qiskit-specific objects to explicit strings or dictionaries.
377Never serialize credentials, service account objects, raw environments, or API request headers.

Experiment Manifest

Persist enough information to reconstruct the workflow:

from importlib.metadata import version

manifest = {
    "packages": {
        "qiskit": version("qiskit"),
        "qiskit-ibm-runtime": version("qiskit-ibm-runtime"),
    },
    "backend": backend.name,
    "job_ids": job_ids,
    "seed_transpiler": 31,
    "optimization_level": 1,
    "shots": 4096,
    "primitive_options": resolved_options,
    "logical_parameter_order": [
        parameter.name for parameter in logical_circuit.parameters
    ],
    "compiled_layout": str(isa_circuit.layout),
    "compiled_operations": dict(isa_circuit.count_ops()),
}

Store logical and ISA circuits in QPY, and store the manifest in a text format such as JSON after converting Qiskit-specific objects to explicit strings or dictionaries.

Never serialize credentials, service account objects, raw environments, or API request headers.

Raw lines 349 to 378
379## Preflight Before Paid Execution
3811. Run the local primitive example.
3822. Validate circuit width, parameter order, and classical-register names.
3833. Check observable width and apply the final layout.
3844. Compile against the exact backend object and inspect native two-qubit operations.
3855. Test against a fake or Aer backend.
3866. Estimate PUB expansion from parameter arrays, observables, mitigation, and twirling.
3877. Select an allowed execution mode and bounded `max_time`.
3888. Persist job IDs immediately.
3899. Analyze the application metric, not only raw counts or expectation values.

Preflight Before Paid Execution

  1. Run the local primitive example.
  2. Validate circuit width, parameter order, and classical-register names.
  3. Check observable width and apply the final layout.
  4. Compile against the exact backend object and inspect native two-qubit operations.
  5. Test against a fake or Aer backend.
  6. Estimate PUB expansion from parameter arrays, observables, mitigation, and twirling.
  7. Select an allowed execution mode and bounded max_time.
  8. Persist job IDs immediately.
  9. Analyze the application metric, not only raw counts or expectation values.
Raw lines 379 to 389