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

circuits.md

319 lines · skills/qiskit/references/circuits.md at 5f2bc03

Open on GitHub
1# Circuits, Parameters, Control Flow, and Serialization
2

Circuits, Parameters, Control Flow, and Serialization

Raw lines 1 to 2
3## Circuit Data Model
4
5`QuantumCircuit` stores ordered quantum bits, classical bits, instructions, parameters, global phase, metadata, and optional real-time classical control flow.
6
7```python
8from qiskit import QuantumCircuit
9
10circuit = QuantumCircuit(3, 3, name="ghz")
11circuit.h(0)
12circuit.cx(0, 1)
13circuit.cx(1, 2)
14circuit.measure([0, 1, 2], [0, 1, 2])
16print(circuit.num_qubits)
17print(circuit.num_clbits)
18print(circuit.depth())
19print(circuit.count_ops())
20```
22Use explicit registers when result names or control-flow operands matter:
24```python
25from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
27qubits = QuantumRegister(2, "q")
28syndrome = ClassicalRegister(1, "syndrome")
29readout = ClassicalRegister(2, "readout")
30circuit = QuantumCircuit(qubits, syndrome, readout)
31```
33Sampler V2 returns one data field per classical register, so meaningful register names improve result handling.

Circuit Data Model

QuantumCircuit stores ordered quantum bits, classical bits, instructions, parameters, global phase, metadata, and optional real-time classical control flow.

from qiskit import QuantumCircuit

circuit = QuantumCircuit(3, 3, name="ghz")
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.measure([0, 1, 2], [0, 1, 2])

print(circuit.num_qubits)
print(circuit.num_clbits)
print(circuit.depth())
print(circuit.count_ops())

Use explicit registers when result names or control-flow operands matter:

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister

qubits = QuantumRegister(2, "q")
syndrome = ClassicalRegister(1, "syndrome")
readout = ClassicalRegister(2, "readout")
circuit = QuantumCircuit(qubits, syndrome, readout)

Sampler V2 returns one data field per classical register, so meaningful register names improve result handling.

Raw lines 3 to 34
35## Bit and Pauli Ordering
37Qiskit uses little-endian conventions:
39- Qubit 0 is conventionally the least-significant qubit.
40- Count strings are printed most-significant classical bit first.
41- The rightmost character of a Pauli label acts on qubit 0.
42- Circuit diagrams normally draw qubit 0 at the top.
44For a two-qubit operator, `"ZI"` applies `Z` to qubit 1 and identity to qubit 0. Never reverse strings based only on visual circuit order.
46When translating a bitstring into graph vertices or variables, write and test an explicit conversion:
48```python
49def qiskit_bitstring_to_classical_values(bitstring: str) -> list[int]:
50 """Decode one register in classical-bit order 0, 1, ..."""
51 return [int(bit) for bit in reversed(bitstring.replace(" ", ""))]
52```
54This yields classical-bit order, not qubit order. Map through each `measure(qubit, clbit)` instruction before interpreting variables. For multiple registers or `join_data`, decode with the recorded register order; blindly stripping spaces does not establish the qubit mapping.

Bit and Pauli Ordering

Qiskit uses little-endian conventions:

  • Qubit 0 is conventionally the least-significant qubit.
  • Count strings are printed most-significant classical bit first.
  • The rightmost character of a Pauli label acts on qubit 0.
  • Circuit diagrams normally draw qubit 0 at the top.

For a two-qubit operator, "ZI" applies Z to qubit 1 and identity to qubit 0. Never reverse strings based only on visual circuit order.

When translating a bitstring into graph vertices or variables, write and test an explicit conversion:

def qiskit_bitstring_to_classical_values(bitstring: str) -> list[int]:
    """Decode one register in classical-bit order 0, 1, ..."""
    return [int(bit) for bit in reversed(bitstring.replace(" ", ""))]

This yields classical-bit order, not qubit order. Map through each measure(qubit, clbit) instruction before interpreting variables. For multiple registers or join_data, decode with the recorded register order; blindly stripping spaces does not establish the qubit mapping.

Raw lines 35 to 55
56## Gates and Instructions
58```python
59from math import pi
60from qiskit import QuantumCircuit
62circuit = QuantumCircuit(3)
64# One-qubit gates
65circuit.x(0)
66circuit.h(1)
67circuit.s(1)
68circuit.t(2)
69circuit.rx(pi / 3, 0)
70circuit.ry(pi / 4, 1)
71circuit.rz(pi / 5, 2)
73# Two- and three-qubit gates
74circuit.cx(0, 1)
75circuit.cz(1, 2)
76circuit.swap(0, 2)
77circuit.ccx(0, 1, 2)
78```
80Prefer high-level gates while constructing the algorithm. Let a target-aware transpiler translate them to the selected backend's instruction set.
82Barriers are directives that constrain some transpiler reordering and optimization. Use them only when the experimental boundary matters, not as visual decoration:
84```python
85circuit.barrier(label="logical-boundary")
86```

Gates and Instructions

from math import pi
from qiskit import QuantumCircuit

circuit = QuantumCircuit(3)

# One-qubit gates
circuit.x(0)
circuit.h(1)
circuit.s(1)
circuit.t(2)
circuit.rx(pi / 3, 0)
circuit.ry(pi / 4, 1)
circuit.rz(pi / 5, 2)

# Two- and three-qubit gates
circuit.cx(0, 1)
circuit.cz(1, 2)
circuit.swap(0, 2)
circuit.ccx(0, 1, 2)

Prefer high-level gates while constructing the algorithm. Let a target-aware transpiler translate them to the selected backend's instruction set.

Barriers are directives that constrain some transpiler reordering and optimization. Use them only when the experimental boundary matters, not as visual decoration:

circuit.barrier(label="logical-boundary")
Raw lines 56 to 87
88## Measurements and Resets
90```python
91from qiskit import QuantumCircuit
93circuit = QuantumCircuit(2, 2)
94circuit.h(0)
95circuit.cx(0, 1)
96circuit.measure([0, 1], [0, 1])
97```
99`measure_all()` defaults to `add_bits=True`, adding a new register even when classical bits already exist (a name collision can change `meas`). Use `add_bits=False` only when sufficient classical bits exist and their ordering is intended:
101```python
102circuit = QuantumCircuit(2)
103circuit.h(0)
104circuit.cx(0, 1)
105circuit.measure_all()
106print([register.name for register in circuit.cregs])
107```
109Sampler requires measurement instructions for sampled classical output. Estimator generally uses circuits without final measurements.
111Use `reset()` only when the execution target supports it:
113```python
114circuit.reset(0)
115```

Measurements and Resets

from qiskit import QuantumCircuit

circuit = QuantumCircuit(2, 2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure([0, 1], [0, 1])

measure_all() defaults to add_bits=True, adding a new register even when classical bits already exist (a name collision can change meas). Use add_bits=False only when sufficient classical bits exist and their ordering is intended:

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
print([register.name for register in circuit.cregs])

Sampler requires measurement instructions for sampled classical output. Estimator generally uses circuits without final measurements.

Use reset() only when the execution target supports it:

circuit.reset(0)
Raw lines 88 to 116
117## Parameterized Circuits
119Primitive PUBs are the preferred way to evaluate one parameterized circuit at many values.
121```python
122import numpy as np
123from qiskit import QuantumCircuit
124from qiskit.circuit import ParameterVector
126theta = ParameterVector("theta", 3)
127circuit = QuantumCircuit(3)
128for qubit, parameter in enumerate(theta):
129 circuit.ry(parameter, qubit)
130circuit.cx(0, 1)
131circuit.cx(1, 2)
133parameter_order = list(circuit.parameters)
134parameter_values = np.array(
135 [
136 [0.0, 0.0, 0.0],
137 [0.1, 0.2, 0.3],
138 [0.4, 0.5, 0.6],
139 ]
142assert parameter_values.shape[-1] == len(parameter_order)
143```
145Do not assume that creation order and `circuit.parameters` order are interchangeable for arbitrary named parameters. Print or persist the order:
147```python
148print([parameter.name for parameter in circuit.parameters])
149```
151For debugging or APIs that require a bound circuit:
153```python
154bound = circuit.assign_parameters(
155 dict(zip(parameter_order, parameter_values[0], strict=True))
157```
159For iterative primitive workloads, keep the circuit parameterized and pass values in the PUB instead of producing and transpiling a bound circuit on every iteration.

Parameterized Circuits

Primitive PUBs are the preferred way to evaluate one parameterized circuit at many values.

import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector

theta = ParameterVector("theta", 3)
circuit = QuantumCircuit(3)
for qubit, parameter in enumerate(theta):
    circuit.ry(parameter, qubit)
circuit.cx(0, 1)
circuit.cx(1, 2)

parameter_order = list(circuit.parameters)
parameter_values = np.array(
    [
        [0.0, 0.0, 0.0],
        [0.1, 0.2, 0.3],
        [0.4, 0.5, 0.6],
    ]
)

assert parameter_values.shape[-1] == len(parameter_order)

Do not assume that creation order and circuit.parameters order are interchangeable for arbitrary named parameters. Print or persist the order:

print([parameter.name for parameter in circuit.parameters])

For debugging or APIs that require a bound circuit:

bound = circuit.assign_parameters(
    dict(zip(parameter_order, parameter_values[0], strict=True))
)

For iterative primitive workloads, keep the circuit parameterized and pass values in the PUB instead of producing and transpiling a bound circuit on every iteration.

Raw lines 117 to 160
161## Composition and Reuse
163```python
164from qiskit import QuantumCircuit
166prepare = QuantumCircuit(2, name="prepare")
167prepare.h(0)
169entangle = QuantumCircuit(2, name="entangle")
170entangle.cx(0, 1)
172combined = prepare.compose(entangle)
173```
175Map qubits explicitly when composing circuits with different widths:
177```python
178larger = QuantumCircuit(4)
179larger.compose(combined, qubits=[1, 3], inplace=True)
180```
182Convert a reusable unitary subcircuit to a gate or instruction:
184```python
185bell_prep = combined.to_gate(label="Bell prep")
186outer = QuantumCircuit(2)
187outer.append(bell_prep, [0, 1])
188```
190Circuits containing measurements or other non-unitary instructions cannot be converted to a `Gate`.

Composition and Reuse

from qiskit import QuantumCircuit

prepare = QuantumCircuit(2, name="prepare")
prepare.h(0)

entangle = QuantumCircuit(2, name="entangle")
entangle.cx(0, 1)

combined = prepare.compose(entangle)

Map qubits explicitly when composing circuits with different widths:

larger = QuantumCircuit(4)
larger.compose(combined, qubits=[1, 3], inplace=True)

Convert a reusable unitary subcircuit to a gate or instruction:

bell_prep = combined.to_gate(label="Bell prep")
outer = QuantumCircuit(2)
outer.append(bell_prep, [0, 1])

Circuits containing measurements or other non-unitary instructions cannot be converted to a Gate.

Raw lines 161 to 191
192## Current Circuit-Library Constructors
194Qiskit 2.x is moving from mutable blueprint classes to functions and gates that build concrete objects immediately.
196```python
197from qiskit import QuantumCircuit
198from qiskit.circuit.library import QFTGate, efficient_su2, real_amplitudes
200ansatz = efficient_su2(
201 num_qubits=4,
202 reps=2,
203 entanglement="linear",
206real_ansatz = real_amplitudes(
207 num_qubits=4,
208 reps=2,
209 entanglement="reverse_linear",
212qft = QuantumCircuit(4)
213qft.append(QFTGate(4), range(4))
214```
216The old `QFT` blueprint class is deprecated as of Qiskit 2.1 and is scheduled for removal in Qiskit 3.0. Use `QFTGate` or `qiskit.synthesis.qft.synth_qft_full`.
218Other current constructors include:
220```python
221from qiskit.circuit.library import (
222 grover_operator,
223 n_local,
224 pauli_feature_map,
225 zz_feature_map,
227```
229Check the current API before using a class copied from an older tutorial; several blueprint classes have function replacements.

Current Circuit-Library Constructors

Qiskit 2.x is moving from mutable blueprint classes to functions and gates that build concrete objects immediately.

from qiskit import QuantumCircuit
from qiskit.circuit.library import QFTGate, efficient_su2, real_amplitudes

ansatz = efficient_su2(
    num_qubits=4,
    reps=2,
    entanglement="linear",
)

real_ansatz = real_amplitudes(
    num_qubits=4,
    reps=2,
    entanglement="reverse_linear",
)

qft = QuantumCircuit(4)
qft.append(QFTGate(4), range(4))

The old QFT blueprint class is deprecated as of Qiskit 2.1 and is scheduled for removal in Qiskit 3.0. Use QFTGate or qiskit.synthesis.qft.synth_qft_full.

Other current constructors include:

from qiskit.circuit.library import (
    grover_operator,
    n_local,
    pauli_feature_map,
    zz_feature_map,
)

Check the current API before using a class copied from an older tutorial; several blueprint classes have function replacements.

Raw lines 192 to 230
231## Dynamic Circuits and Classical Control
233Qiskit expresses structured real-time control flow with context managers:
235```python
236from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
238qubit = QuantumRegister(1, "q")
239flag = ClassicalRegister(1, "flag")
240circuit = QuantumCircuit(qubit, flag)
242circuit.h(qubit[0])
243circuit.measure(qubit[0], flag[0])
244with circuit.if_test((flag[0], True)):
245 circuit.x(qubit[0])
246```
248Other structured builders include `if_test`, `while_loop`, `for_loop`, and `switch`.
250Before executing dynamic circuits:
2521. Confirm the selected backend target includes the required control-flow operations.
2532. Transpile against that exact backend.
2543. Check current compatibility among dynamic circuits, fractional gates, and mitigation options.
2554. Test classical-register interpretation locally or on a fake backend.
257Legacy `instruction.c_if(...)` patterns were removed in Qiskit 2.0.

Dynamic Circuits and Classical Control

Qiskit expresses structured real-time control flow with context managers:

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister

qubit = QuantumRegister(1, "q")
flag = ClassicalRegister(1, "flag")
circuit = QuantumCircuit(qubit, flag)

circuit.h(qubit[0])
circuit.measure(qubit[0], flag[0])
with circuit.if_test((flag[0], True)):
    circuit.x(qubit[0])

Other structured builders include if_test, while_loop, for_loop, and switch.

Before executing dynamic circuits:

  1. Confirm the selected backend target includes the required control-flow operations.
  2. Transpile against that exact backend.
  3. Check current compatibility among dynamic circuits, fractional gates, and mitigation options.
  4. Test classical-register interpretation locally or on a fake backend.

Legacy instruction.c_if(...) patterns were removed in Qiskit 2.0.

Raw lines 231 to 258
259## Circuit Inspection
261```python
262print("qubits:", circuit.num_qubits)
263print("classical bits:", circuit.num_clbits)
264print("parameters:", [parameter.name for parameter in circuit.parameters])
265print("depth:", circuit.depth())
266print("size:", circuit.size())
267print("operations:", circuit.count_ops())
268print("nonlocal gates:", circuit.num_nonlocal_gates())
269```
271These metrics are structural, not direct fidelity or cost estimates. Recompute them after target-aware transpilation.

Circuit Inspection

print("qubits:", circuit.num_qubits)
print("classical bits:", circuit.num_clbits)
print("parameters:", [parameter.name for parameter in circuit.parameters])
print("depth:", circuit.depth())
print("size:", circuit.size())
print("operations:", circuit.count_ops())
print("nonlocal gates:", circuit.num_nonlocal_gates())

These metrics are structural, not direct fidelity or cost estimates. Recompute them after target-aware transpilation.

Raw lines 259 to 272
273## QPY Serialization
275QPY preserves Qiskit circuits more faithfully than interchange formats intended for other tools:
277```python
278from pathlib import Path
279from qiskit import qpy
281path = Path("experiment.qpy")
282with path.open("wb") as output_file:
283 qpy.dump(circuit, output_file)
285with path.open("rb") as input_file:
286 loaded_circuits = qpy.load(input_file)
288loaded = loaded_circuits[0]
289```
291QPY is forward-compatible: newer Qiskit releases can normally load older QPY files. Older releases are not expected to load QPY produced by newer versions.
293Record the writing Qiskit version and retain source code for long-lived artifacts. Treat all external binary inputs as untrusted and enforce source, size, and version policies. Never substitute Python pickle for untrusted circuit data.

QPY Serialization

QPY preserves Qiskit circuits more faithfully than interchange formats intended for other tools:

from pathlib import Path
from qiskit import qpy

path = Path("experiment.qpy")
with path.open("wb") as output_file:
    qpy.dump(circuit, output_file)

with path.open("rb") as input_file:
    loaded_circuits = qpy.load(input_file)

loaded = loaded_circuits[0]

QPY is forward-compatible: newer Qiskit releases can normally load older QPY files. Older releases are not expected to load QPY produced by newer versions.

Record the writing Qiskit version and retain source code for long-lived artifacts. Treat all external binary inputs as untrusted and enforce source, size, and version policies. Never substitute Python pickle for untrusted circuit data.

Raw lines 273 to 294
295## OpenQASM Interchange
297Use OpenQASM when interoperability is more important than preserving every Qiskit-specific object:
299```python
300from qiskit import qasm2, qasm3
302qasm2_text = qasm2.dumps(circuit)
303round_tripped = qasm2.loads(qasm2_text)
305qasm3_text = qasm3.dumps(circuit)
306```
308OpenQASM 2 cannot represent all modern control-flow and classical-expression features. OpenQASM 3 import requires optional tooling and may not round-trip Qiskit metadata or custom instructions. Validate semantics after interchange.

OpenQASM Interchange

Use OpenQASM when interoperability is more important than preserving every Qiskit-specific object:

from qiskit import qasm2, qasm3

qasm2_text = qasm2.dumps(circuit)
round_tripped = qasm2.loads(qasm2_text)

qasm3_text = qasm3.dumps(circuit)

OpenQASM 2 cannot represent all modern control-flow and classical-expression features. OpenQASM 3 import requires optional tooling and may not round-trip Qiskit metadata or custom instructions. Validate semantics after interchange.

Raw lines 295 to 309
310## Common Circuit Mistakes
312- **Wrong output register**: inspect `circuit.cregs` and use the corresponding Sampler result field.
313- **Reversed interpretation**: account for count-string and Pauli-label ordering explicitly.
314- **Parameter shape mismatch**: make the final value-array dimension equal `len(circuit.parameters)`.
315- **Duplicate measurements**: use `remove_final_measurements()` before adding a new measurement scheme.
316- **Estimator failure**: remove final measurements and non-unitary instructions.
317- **Unsupported control flow**: inspect `backend.operation_names` and `backend.target`.
318- **Circuit wider than target**: compare `circuit.num_qubits` with `backend.num_qubits` before transpiling.
319- **Unexpected optimization across boundaries**: add a barrier only when that behavior is intentional.

Common Circuit Mistakes

  • Wrong output register: inspect circuit.cregs and use the corresponding Sampler result field.
  • Reversed interpretation: account for count-string and Pauli-label ordering explicitly.
  • Parameter shape mismatch: make the final value-array dimension equal len(circuit.parameters).
  • Duplicate measurements: use remove_final_measurements() before adding a new measurement scheme.
  • Estimator failure: remove final measurements and non-unitary instructions.
  • Unsupported control flow: inspect backend.operation_names and backend.target.
  • Circuit wider than target: compare circuit.num_qubits with backend.num_qubits before transpiling.
  • Unexpected optimization across boundaries: add a barrier only when that behavior is intentional.
Raw lines 310 to 319