Quantum gates in Python
A quantum gate is just a unitary matrix (U†U = I) acting on a qubit state vector. Single-qubit gates are 2×2; two-qubit gates are 4×4. Apply a gate by matrix–vector multiplication. Measurement is not a gate — it’s a separate, probabilistic, non-unitary projection.
Single-qubit basics
import numpy as np
# Computational basis
KET_0 = np.array([[1], [0]], dtype=complex)
KET_1 = np.array([[0], [1]], dtype=complex)
# Pauli gates
X = np.array([[0, 1],
[1, 0]], dtype=complex) # bit-flip
Y = np.array([[0, -1j],
[1j, 0]], dtype=complex) # bit + phase flip
Z = np.array([[1, 0],
[0, -1]], dtype=complex) # phase-flip
# Hadamard — creates superposition
H = (1 / np.sqrt(2)) * np.array([[1, 1],
[1, -1]], dtype=complex)
# Phase gates
S = np.array([[1, 0], [0, 1j]], dtype=complex) # √Z
T = np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]], complex) # √S
Apply a gate the obvious way:
X @ KET_0 # → |1⟩
H @ KET_0 # → (|0⟩ + |1⟩) / √2 — equal superposition
Z @ (H @ KET_0) # → (|0⟩ − |1⟩) / √2
Sanity-check it’s unitary:
assert np.allclose(H.conj().T @ H, np.eye(2))
Multi-qubit gates
Multi-qubit states live in the tensor product of single-qubit spaces. Use np.kron:
# |10⟩ = |1⟩ ⊗ |0⟩
KET_10 = np.kron(KET_1, KET_0)
# CNOT: flip target (qubit 1) iff control (qubit 0) is |1⟩
CNOT = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0],
], dtype=complex)
CNOT @ KET_10 # → |11⟩
To apply a single-qubit gate to one qubit of a multi-qubit register, lift it with np.kron against identities:
I = np.eye(2, dtype=complex)
H_on_q0 = np.kron(H, I) # H acts on qubit 0, identity on qubit 1
H_on_q1 = np.kron(I, H) # ... and vice versa
A two-qubit Bell state, end-to-end
psi = np.kron(KET_0, KET_0) # |00⟩
psi = np.kron(H, I) @ psi # H on q0 → (|00⟩ + |10⟩)/√2
psi = CNOT @ psi # entangle → (|00⟩ + |11⟩)/√2
That’s the Bell pair |Φ⁺⟩ — maximally entangled.
When to switch to a library
NumPy is great for learning and small circuits. Beyond ~20 qubits the state vector blows up (2ⁿ complex amplitudes). Switch to Qiskit, Cirq, or PennyLane for real work — they handle circuit composition, simulators, hardware backends, and noise models:
from qiskit import QuantumCircuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
# qc now describes the same Bell-pair circuit as above.
Note Qiskit uses little-endian qubit ordering (q0 is the rightmost bit in tensor products), which can flip the matrices compared to the textbook convention used here.