Notebook 12 — Quantum basics: qubits as complex vectors#
Goal: build intuition for why an n-qubit state is just a length-2^n complex vector, and see the Bell state in action. No prior quantum background required.
From bits to qubits#
A classical bit is either 0 or 1. A qubit is a unit vector \(\alpha|0\rangle + \beta|1\rangle\) in \(\mathbb{C}^2\), with \(|\alpha|^2 + |\beta|^2 = 1\). We call \(|\alpha|^2\) and \(|\beta|^2\) the probabilities of measuring 0 or 1 respectively.
\(n\) qubits: a vector of length \(2^n\). That’s it — the whole “weirdness” is in linear algebra over complex numbers.
import numpy as np
import matplotlib.pyplot as plt
from pqc_edu.quantum.simulator import QuantumState, H, X, Z, CNOT_MATRIX, Rk
A one-qubit state#
Create \(|0\rangle\), then apply a Hadamard to put it in equal superposition.
q = QuantumState(1)
print('before:', q.amps)
q.apply_1q(H, 0)
print('after H:', q.amps)
print('probability of 0:', abs(q.amps[0])**2)
print('probability of 1:', abs(q.amps[1])**2)
before: [1.+0.j 0.+0.j]
after H: [0.70710678+0.j 0.70710678+0.j]
probability of 0: 0.4999999999999999
probability of 1: 0.4999999999999999
Measuring collapses the state#
Measurement is the only non-unitary step. After H, a measurement picks 0 or 1 each with 50% probability.
rng = np.random.default_rng(0)
counts = [0, 0]
for _ in range(1000):
q = QuantumState(1)
q.apply_1q(H, 0)
counts[q.measure([0], rng)] += 1
print('0 count:', counts[0], ' 1 count:', counts[1])
print('roughly balanced:', abs(counts[0] - counts[1]) < 100)
0 count: 473 1 count: 527
roughly balanced: True
Entanglement: the Bell state#
Two qubits. Apply H to the first, then CNOT with the first as control. The result is \((|00\rangle + |11\rangle)/\sqrt{2}\) — a maximally entangled state. Measuring one qubit forces the outcome on the other.
rng = np.random.default_rng(1)
outcomes = []
for _ in range(500):
q = QuantumState(2)
q.apply_1q(H, 0)
q.apply_controlled_1q(X, ctrl=0, target=1)
outcomes.append(q.measure([0, 1], rng))
print('counts:', {i: outcomes.count(i) for i in range(4)})
print('only |00> (0) and |11> (3) appear:', set(outcomes) <= {0, 3})
counts: {0: 259, 1: 0, 2: 0, 3: 241}
only |00> (0) and |11> (3) appear: True
What’s next#
We just made a simulator that reproduces one of the most counter-intuitive results in physics with ~30 lines of numpy. The next notebook builds the QFT — the key ingredient that lets Shor’s algorithm recover periods.
→ 13_qft_and_period.ipynb