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.

← gates