Chapter 4 — Key Takeaways (Multi-Qubit Programming)
The two-qubit reference. Part IV is built from this page plus Chapter 3's gate table.
Tensor product and ordering
$$|\psi\rangle = |q_{n-1}\rangle \otimes \cdots \otimes |q_1\rangle \otimes |q_0\rangle$$
Qubit 0 is LAST in the tensor product and RIGHTMOST in the bitstring. Reverse of most textbooks.
np.kron(zero, one) == Statevector(QuantumCircuit(2).x(0)).data # both [0,1,0,0]
| Circuit | Amplitude on indices | Labels |
|---|---|---|
h(0) |
0, 1 | '00', '01' |
h(1) |
0, 2 | '00', '10' |
Use probabilities_dict() whenever the index needs a meaning. Reserve raw arrays for numerical
work. Test with asymmetric states — symmetric ones hide endianness bugs.
| Qubits | General state | Product state |
|---|---|---|
| $n$ | $2^n$ amplitudes | $2n$ numbers |
CNOT
qc.cx(control, target) # flips target iff control is 1
In (q1 q0) |
Out, after cx(0, 1) |
|---|---|
00 |
00 |
01 |
11 |
10 |
10 |
11 |
01 |
cx(0,1) cx(1,0) ← the textbook matrix
[[1 0 0 0] [[1 0 0 0]
[0 0 0 1] [0 1 0 0]
[0 0 1 0] [0 0 0 1]
[0 1 0 0]] [0 0 1 0]]
Both are correct CNOTs; the difference is endianness.
On a superposition CNOT acts on both branches, producing a state that does not factorize. That non-factorizability is entanglement.
The four Bell states
qc.x(1) if Psi # then:
qc.h(0)
qc.z(0) if minus
qc.cx(0, 1)
| State | Counts | |
|---|---|---|
| $\lvert\Phi^+\rangle$ | $\tfrac1{\sqrt2}(\lvert00\rangle+\lvert11\rangle)$ | 00, 11 |
| $\lvert\Phi^-\rangle$ | $\tfrac1{\sqrt2}(\lvert00\rangle-\lvert11\rangle)$ | 00, 11 |
| $\lvert\Psi^+\rangle$ | $\tfrac1{\sqrt2}(\lvert01\rangle+\lvert10\rangle)$ | 01, 10 |
| $\lvert\Psi^-\rangle$ | $\tfrac1{\sqrt2}(\lvert01\rangle-\lvert10\rangle)$ | 01, 10 |
$\Phi$ vs $\Psi$ shows in the counts. The $\pm$ does not.
Bell measurement = run the preparation backward (cx(0,1) then h(0)) before measuring → the
four states map to 00, 01, 10, 11 deterministically. The primitive behind teleportation and
superdense coding (Ch. 9).
★ Testing for entanglement
from qiskit.quantum_info import Statevector, partial_trace, entropy
rho0 = partial_trace(Statevector(qc), [1]) # keep qubit 0
ent = float(entropy(rho0)) # 0 = product, 1 = maximal
| Product state | Bell state | |
|---|---|---|
| purity $\mathrm{Tr}(\rho^2)$ | 1.0 | 0.5 (the floor) |
| entanglement entropy | 0.0 | 1.0 (maximal) |
| Bloch vector length | 1.0 | 0.0 |
A qubit of a Bell state sits at the CENTER of the Bloch sphere — completely undetermined on its own, while the pair is in a completely definite, zero-entropy state. All the information is in the correlation and none is in the parts. It is not "in superposition."
⚠️ Counts never prove entanglement
An impostor with zero entanglement gives identical Z-basis counts:
qc.h(0); qc.measure(0, 0)
with qc.if_test((qc.clbits[0], 1)):
qc.x(1)
| Bell | Impostor | |
|---|---|---|
| Z basis | {'00': 2074, '11': 2022} |
{'00': 2031, '11': 2065} |
X basis (add h to both) |
{'00': 2074, '11': 2022} |
uniform over all four |
More shots cannot help — same distribution, measured more precisely.
The witness (the real standard):
$$W = \langle Z\otimes Z\rangle + \langle X\otimes X\rangle, \qquad W \le 1 \text{ for every separable state}, \quad W = 2 \text{ for } |\Phi^+\rangle$$
| $W$ | |
|---|---|
| Bell, ideal | 2.0000 |
| Impostor | 0.9966 |
| Bell on device-derived noise (opt 3) | 1.9307 |
| best over 20,000 random product states | 0.9980 |
Gate costs — memorize these
| Construct | CNOTs | Depth |
|---|---|---|
| CNOT, CZ | 1 | 1 |
| SWAP | 3 | 3 |
| Toffoli (CCX) | 6 | 11 |
| C3X | 14 | 28 |
| C4X | 36 | 65 |
Add 3 CNOTs per SWAP the router inserts for non-adjacent qubits. You rarely write swap — the
transpiler inserts it, invisibly, and it is the largest hidden cost in quantum programming.
CZ is symmetric (no control/target). $H_{(1)}\,\mathrm{CX}(0,1)\,H_{(1)} = \mathrm{CZ}$, because $HXH = Z$.
GHZ vs W
ghz: h(0); cx(0,1); cx(1,2); ... # (|00…0⟩ + |11…1⟩)/√2
| Lose one qubit → the survivors are | |
|---|---|
| GHZ | a classical mixture, no entanglement (eigenvalues 0.5, 0.5, 0, 0) — fragile |
| W | still entangled, just less so (eigenvalues 0.667, 0.333, 0, 0) — robust |
Not interconvertible by local operations: genuinely different kinds of tripartite entanglement. GHZ's fragility makes it a good benchmark (Ch. 30).
The exponential wall, refined
Product states cost $2n$; general states cost $2^n$. The cost is entanglement, in proportion to how much there is — which is exactly what matrix product state simulation exploits (Ch. 11).
An algorithm that does not generate substantial entanglement is classically simulable and cannot give a quantum advantage.
📉 Hardware: layout decides correctness
GHZ correct-fraction on a device-derived model, 4096 shots:
| $n$ | opt level 1 | opt level 3 |
|---|---|---|
| 2 | 0.9561 | 0.9827 |
| 4 | 0.9084 | 0.9585 |
| 6 | 0.8127 | 0.8850 |
| 7 | 0.2148 | 0.8906 |
| 8 | 0.1091 | 0.8569 |
The cliff is a broken physical qubit (readout error 0.257, ECR error 1.0), not scaling. Levels 0–1 pick qubits by position; levels 2–3 pick them by measured error rate.
Preflight, always:
isa.layout.final_index_layout() # RECORD THIS with every result
backend.target["measure"][(q,)].error # > 0.1 → unusable
backend.target["ecr"][(a, b)].error # > 0.05 → suspect; 1.0 → dead
Common pitfalls
- Reading
sv.data[2]and assuming you know which state that is. - Testing with symmetric states, which hide every endianness bug.
- Inferring entanglement from computational-basis counts.
- Taking more shots to fix a wrong measurement setting.
- Trusting optimization level 1 for a result you will report.
- Reporting a hardware result without the layout.
- Reading "correct fraction" as evidence of coherence — a classical mixture scores 100%.
Project piece added this chapter
vqelab/circuits.py v1 — two_qubit_ansatz() (rotation layer → entangling layer → rotation
layer), plus entanglement_entropy(), is_entangled(), and reachable_entanglement().
Without the CNOT the qubits stay in a product state forever, and a product-state ansatz can only
represent product-state solutions — which for a molecular ground state is exactly wrong.
reachable_entanglement() is the first diagnostic to run when a VQE will not converge.