Quantum Entanglement
Spooky action at a distance, decoded — from Bell's theorem and the EPR paradox to teleportation, superdense coding, and why entanglement is the fuel behind quantum computational advantage.
1. What is entanglement?
A classical system of two bits has four possible states: 00, 01, 10, 11. A quantum system of two qubits lives in a four-dimensional complex vector space spanned by $|00\rangle, |01\rangle, |10\rangle, |11\rangle$. The general two-qubit state is a superposition:
with $|\alpha_{00}|^2 + |\alpha_{01}|^2 + |\alpha_{10}|^2 + |\alpha_{11}|^2 = 1$. Such a state is called a product state (or separable) if it can be written as a tensor product of single-qubit states:
If no such factorisation exists, the state is entangled. Entanglement is not a rare edge case — most states in a two-qubit Hilbert space are entangled.
$|\Phi^+\rangle = \frac{1}{\sqrt{2}}(|00\rangle + |11\rangle)$ is entangled. If Alice measures qubit A and gets 0, Bob's qubit instantly collapses to $|0\rangle$. If she gets 1, Bob's qubit collapses to $|1\rangle$. The correlation is perfect — and holds no matter how far apart Alice and Bob are. Einstein called it spooky action at a distance and believed it meant quantum mechanics was incomplete. Bell (1964) proved him wrong.
Why it is not magic or faster-than-light
Alice's measurement result is genuinely random — she cannot force herself to get 0 or 1. To verify the correlation, Alice and Bob must compare notes via a classical channel. Until they do, neither knows whether any correlation exists. No information has travelled faster than light.
More formally: Bob's local reduced density matrix $\rho_B = \text{tr}_A(|\Phi^+\rangle\langle\Phi^+|) = \frac{1}{2}I$ is a completely mixed state regardless of whether Alice has measured. From Bob's local perspective, his qubit is maximally random before and after Alice's measurement. The correlations are real but only accessible by comparing classical records.
Product vs entangled: a quick test
For a two-qubit pure state with amplitudes written as a $2\times 2$ matrix $M$ (where $M_{ij} = \alpha_{ij}$), the state is a product state if and only if $\det(M) = 0$. For $|\Phi^+\rangle$:
Entangled. For $|01\rangle$: $M = \begin{pmatrix}0 & 1 \\ 0 & 0\end{pmatrix}$, $\det(M) = 0$. Product state.
2. Bell states
The four Bell states (also called EPR pairs or maximally entangled states) form an orthonormal basis for the two-qubit Hilbert space:
Each is "maximally entangled": measuring either qubit in any basis gives 50/50 outcomes, yet the two outcomes are perfectly correlated (or anti-correlated). The entanglement entropy of each Bell state is exactly 1 ebit — the maximum possible for a two-qubit system.
Preparing $|\Phi^+\rangle$ from $|00\rangle$
The circuit is two gates:
- Apply the Hadamard gate $H$ to qubit 1: $|00\rangle \to \frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)|0\rangle = \frac{1}{\sqrt{2}}(|00\rangle + |10\rangle)$
- Apply CNOT with qubit 1 as control, qubit 2 as target: $|00\rangle \to |00\rangle$, $|10\rangle \to |11\rangle$, giving $\frac{1}{\sqrt{2}}(|00\rangle + |11\rangle) = |\Phi^+\rangle$
All four Bell states from $|00\rangle$
| Bell state | Circuit (after H on q1, CNOT) | Bell measurement outcome (q1, q2) | Correlation |
|---|---|---|---|
| $|\Phi^+\rangle = \frac{1}{\sqrt{2}}(|00\rangle+|11\rangle)$ | Start from $|00\rangle$ — no extra gates | 00 | Same parity, same sign |
| $|\Phi^-\rangle = \frac{1}{\sqrt{2}}(|00\rangle-|11\rangle)$ | Apply $Z$ to q1 before H+CNOT | 10 | Same parity, flipped sign |
| $|\Psi^+\rangle = \frac{1}{\sqrt{2}}(|01\rangle+|10\rangle)$ | Apply $X$ to q2 after CNOT | 01 | Opposite parity, same sign |
| $|\Psi^-\rangle = \frac{1}{\sqrt{2}}(|01\rangle-|10\rangle)$ | Apply $Z$ to q1 and $X$ to q2 | 11 | Opposite parity, flipped sign |
The Bell measurement (used in teleportation and superdense coding) distinguishes the four Bell states perfectly. Protocol: apply CNOT (q1 control, q2 target), then H to q1, then measure both in the computational basis. The 2-bit outcome uniquely identifies which Bell state you had.
3. The EPR paradox and Bell inequalities
Einstein-Podolsky-Rosen (1935)
Einstein, Podolsky, and Rosen published a famous thought experiment arguing that quantum mechanics is incomplete. Their logic: if two particles are entangled and we measure one, the other instantly "knows" its correlated value — even if they are light-years apart. They called this a violation of "local realism":
- Realism: physical properties exist before and independent of measurement.
- Locality: measuring one particle cannot instantly affect a distant particle.
Their conclusion: quantum mechanics must be missing something — hidden variables that predetermine the outcomes. The particles secretly carry their correlated values all along; quantum mechanics just doesn't have access to them.
Bell's theorem (1964)
John Bell turned the EPR philosophical argument into a testable prediction. He showed that any local hidden variable (LHV) theory must satisfy a specific mathematical constraint — the CHSH inequality:
where $E(a,b)$ is the correlation function — the expected value of the product of Alice's and Bob's measurement outcomes ($\pm 1$) when Alice measures along axis $a$ and Bob along axis $b$.
For two qubits in the state $|\Phi^+\rangle$, the quantum correlation function at measurement angles $a$ and $b$ (in the $xz$-plane of the Bloch sphere) is:
where $\sigma_a = \cos(a)\,\sigma_z + \sin(a)\,\sigma_x$. For the optimal measurement angles $a = 0°$, $a' = 90°$, $b = 45°$, $b' = 135°$:
$2\sqrt{2} > 2$. Quantum mechanics violates the Bell inequality. This is the Tsirelson bound — the maximum quantum violation of CHSH.
Aspect (1982): First loophole-free-ish Bell test with polarised photons. Measured $S \approx 2.697$, violating the classical bound. Hensen et al. (2015): First truly loophole-free Bell test (both locality and detection loopholes closed simultaneously) using nitrogen-vacancy centres in diamond, separated by 1.3 km. Measured $S = 2.42 \pm 0.20$. Conclusion: local hidden variables are ruled out. The universe is non-local in its correlations — though not in its signalling.
What Bell's theorem actually says
Bell's result does not say that information travels faster than light. It says that the correlations between distant measurements cannot be explained by any pre-agreed strategy the particles could have set up before they separated. The correlations are genuinely "more correlated" than anything classical probability allows — but they still cannot be used to send a message.
4. Quantum teleportation
Teleportation transmits an unknown qubit state $|\psi\rangle = \alpha|0\rangle + \beta|1\rangle$ from Alice to Bob using a pre-shared Bell pair and 2 classical bits. It does not transmit matter or energy; it transmits quantum information.
Resources: 1 ebit (one shared $|\Phi^+\rangle$ pair) + 2 classical bits.
The protocol, step by step
Label the qubits: Alice holds qubit 1 (the unknown state $|\psi\rangle$) and qubit 2 (her half of the Bell pair). Bob holds qubit 3 (his half of the Bell pair).
Initial state:
Step 1 — Alice applies CNOT (q1 control, q2 target):
Step 2 — Alice applies H to qubit 1:
Regrouping by Alice's 2-bit outcome (qubits 1,2) and Bob's qubit 3:
Step 3 — Alice measures qubits 1 and 2, getting one of $\{00, 01, 10, 11\}$ with equal probability $1/4$.
Step 4 — Alice sends 2 classical bits to Bob (this takes time; no FTL).
Step 5 — Bob applies a correction:
| Alice's result | Bob's qubit state | Bob applies | Final state |
|---|---|---|---|
| 00 | $\alpha|0\rangle + \beta|1\rangle$ | $I$ (nothing) | $|\psi\rangle$ |
| 01 | $\beta|0\rangle + \alpha|1\rangle$ | $X$ | $|\psi\rangle$ |
| 10 | $\alpha|0\rangle - \beta|1\rangle$ | $Z$ | $|\psi\rangle$ |
| 11 | $-\beta|0\rangle + \alpha|1\rangle$ | $ZX$ | $|\psi\rangle$ |
After the correction, Bob's qubit is in state $|\psi\rangle = \alpha|0\rangle + \beta|1\rangle$ — exactly Alice's original state. The unknown state has been "teleported". Alice's qubit has been destroyed in the process (she measured it), as required by the no-cloning theorem.
Until Bob receives Alice's 2 classical bits, his qubit is in a completely mixed state — all four outcomes are equally likely and he cannot extract $\alpha$ or $\beta$ from it alone. The classical communication is not optional. The protocol uses both quantum (the ebit) and classical (2 bits) resources.
5. Superdense coding
Superdense coding is the dual of teleportation. Where teleportation uses 1 ebit + 2 classical bits to send 1 qubit of quantum information, superdense coding uses 1 ebit + 1 qubit to send 2 classical bits.
Protocol
Alice and Bob share the Bell state $|\Phi^+\rangle = \frac{1}{\sqrt{2}}(|00\rangle + |11\rangle)$. Alice holds qubit A, Bob holds qubit B.
Alice wants to send two classical bits $b_1 b_2$. She applies a single-qubit gate to her qubit A based on the 2-bit message:
| Message $b_1 b_2$ | Alice applies to qubit A | Joint state after Alice's gate |
|---|---|---|
| 00 | $I$ (identity) | $|\Phi^+\rangle = \frac{1}{\sqrt{2}}(|00\rangle+|11\rangle)$ |
| 01 | $X$ (bit-flip) | $|\Psi^+\rangle = \frac{1}{\sqrt{2}}(|01\rangle+|10\rangle)$ |
| 10 | $Z$ (phase-flip) | $|\Phi^-\rangle = \frac{1}{\sqrt{2}}(|00\rangle-|11\rangle)$ |
| 11 | $ZX$ | $|\Psi^-\rangle = \frac{1}{\sqrt{2}}(|01\rangle-|10\rangle)$ |
Alice sends her qubit A to Bob (one qubit transmission). Bob now holds both qubits and performs a Bell measurement (CNOT then H on qubit A, then measure both). The four Bell states are orthogonal, so Bob perfectly distinguishes them and recovers $b_1 b_2$.
Why this beats the classical bound
Holevo's theorem says that $n$ qubits can encode at most $n$ classical bits reliably. One qubit alone, without the pre-shared ebit, cannot reliably convey more than 1 bit. The ebit "unlocks" the extra bit — it effectively doubles the classical capacity of the quantum channel. The pre-shared entanglement is the resource being consumed; Alice encodes her 2 bits by choosing which Bell state the pair is in.
6. Entanglement entropy
How do we quantify how much a state is entangled? For a bipartite pure state $|\psi\rangle_{AB}$, the answer is the entanglement entropy (also called von Neumann entropy of the reduced state):
where the reduced density matrix $\rho_A$ is obtained by tracing out subsystem B:
Properties
- $S(A) = 0$ if and only if $|\psi\rangle$ is a product state (no entanglement).
- $S(A) = \log_2 d_A$ (maximum) if and only if $\rho_A \propto I$ (maximally mixed) — maximal entanglement.
- For a two-qubit system, $d_A = 2$ so the maximum entanglement entropy is 1 ebit.
- $S(A) = S(B)$ for any pure bipartite state.
Example: $|\Phi^+\rangle$
Schmidt decomposition
Every bipartite pure state $|\psi\rangle_{AB}$ can be written in the Schmidt form:
where the $\lambda_i > 0$ are called Schmidt coefficients (with $\sum_i \lambda_i^2 = 1$), $\{|i_A\rangle\}$ and $\{|i_B\rangle\}$ are orthonormal bases for A and B respectively, and $r$ is the Schmidt rank. The entanglement entropy equals $S(A) = -\sum_i \lambda_i^2 \log_2 \lambda_i^2$.
Entanglement entropy — every symbol decoded
- $\rho_A$
- Reduced density matrix of subsystem A, obtained by tracing out B. Encodes all local statistics accessible to Alice alone.
- $\operatorname{tr}_B$
- Partial trace over subsystem B. Sums over Bob's basis states, yielding a matrix that acts only on Alice's Hilbert space.
- $S(A)$
- Von Neumann entropy of $\rho_A$, measured in ebits. Zero for product states, one for maximally entangled two-qubit states. For classical probability distributions, this reduces to Shannon entropy. The quantum version generalises this to density matrices.
- $\lambda_i$
- Schmidt coefficients. Their squares are the eigenvalues of $\rho_A$ (and also of $\rho_B$). Computing the Schmidt decomposition is equivalent to doing an SVD on the coefficient matrix $M_{ij} = \alpha_{ij}$.
- Schmidt rank $r$
- Number of non-zero Schmidt coefficients. Schmidt rank 1 means product state. Rank $> 1$ means entangled. A state that is "a little entangled" has one dominant Schmidt coefficient and several small ones. Tensor network methods exploit small Schmidt rank to compress quantum states.
- ebit
- Unit of entanglement. One ebit is the entanglement in one Bell pair. Entanglement entropy in ebits quantifies how many Bell pairs could be distilled from many copies of the state.
Analogy Think of entanglement entropy as measuring "how much the two subsystems have fused together." A Schmidt rank-1 state is like two separate books on a shelf — you can describe each one independently. A maximally entangled state is like a book where every page in volume A is a cipher whose meaning depends on the corresponding page in volume B. Entanglement entropy counts how many cipher-page pairs there are (in bits).
7. Multipartite entanglement
For three or more qubits, entanglement becomes richer — there are qualitatively different ways for multiple particles to be entangled, and not all of them can be converted into each other by local operations and classical communication (LOCC).
GHZ states
The Greenberger-Horne-Zeilinger state generalises the Bell pair to three qubits:
Circuit: H on q1, CNOT(q1, q2), CNOT(q1, q3).
The GHZ state has a striking "all-or-nothing" property: measuring any single qubit in the $Z$ basis instantly determines all the others. If qubit 1 collapses to $|0\rangle$, then qubits 2 and 3 also collapse to $|0\rangle$; if it collapses to $|1\rangle$, all three are $|1\rangle$. The three-qubit correlations violate local realism more dramatically than Bell pairs, making GHZ states useful for quantum secret sharing (a classical secret split into three shares, recoverable only with all three).
However, GHZ states are fragile: tracing out any single qubit leaves the remaining two separable (classically correlated, not entangled). Losing one qubit destroys all quantum correlations.
W states
The W state represents a fundamentally different type of three-qubit entanglement:
It encodes a single excitation distributed equally among three qubits. If one qubit is traced out (or lost), the remaining two are still entangled — the W state is robust to particle loss. This makes W states preferable for quantum networks where qubit loss is likely.
GHZ vs W — two inequivalent classes
| Property | GHZ state | W state |
|---|---|---|
| Entanglement after losing one qubit | Zero (separable remainder) | Still entangled (Bell pair remains) |
| Max violation of Mermin inequality | $2^{(n-1)/2}$ (exponential) | Lower |
| Convertible by LOCC? | No — they are inequivalent LOCC classes | |
| Applications | Quantum secret sharing, GHZ tests of local realism | Quantum networks, quantum memory |
| Preparation complexity | $n-1$ CNOT gates | $O(n)$ gates with ancilla tricks |
The LOCC-inequivalence means no sequence of local operations (operations on individual qubits) plus classical communication can convert a GHZ state into a W state or vice versa with certainty, even probabilistically. They are fundamentally different resources.
8. Entanglement as a computational resource
Entanglement is necessary but not sufficient for quantum advantage. Understanding exactly what role it plays requires separating two related but distinct questions: what makes a quantum circuit simulatable classically, and what produces quantum speedup?
Gottesman-Knill theorem
Clifford circuits — those built from $H$, $S$, $CNOT$, and Pauli gates — can be efficiently simulated classically using the stabiliser formalism, even though these circuits can create Bell pairs and GHZ states. Entanglement alone does not confer computational advantage over classical computers.
Adding a single non-Clifford gate — specifically the $T = \text{diag}(1, e^{i\pi/4})$ gate — makes the gate set universal for quantum computation. $T$ gates produce "magic states" that cannot be simulated by the stabiliser formalism. The combination of entanglement and magic states is what drives quantum speedup.
Entanglement entropy and classical simulation cost
For a quantum circuit acting on $n$ qubits, the cost of simulating it classically using tensor network methods (MPS / DMRG) scales exponentially with the maximum entanglement entropy produced. Specifically, simulating a state with Schmidt rank $r$ across any bipartition requires storing $O(r^2)$ numbers, and the bond dimension $\chi$ (related to Schmidt rank) determines the computational cost as $O(n \chi^3)$.
- Low entanglement circuits (bounded entanglement entropy, as in 1D local circuits near the ground state) are efficiently simulatable classically.
- Random quantum circuits generate entanglement that grows as $O(n)$ after $O(n)$ layers — requiring exponentially large tensor networks to simulate. This is the regime where quantum computers beat classical computers on sampling tasks (quantum supremacy experiments).
In the resource theory of entanglement, Bell pairs are the "gold standard" unit of entanglement. Any entangled state can be characterised by how many Bell pairs it is equivalent to (entanglement of formation), and many quantum information tasks have precise ebit costs. Quantum teleportation costs 1 ebit + 2 classical bits. Superdense coding earns back 2 classical bits from 1 ebit + 1 qubit. The accounting is exact.
9. Interactive: Bell inequality game
The CHSH game makes Bell's theorem concrete. Two players — Alice and Bob — are separated and cannot communicate. A referee sends each player a random input bit ($x$ to Alice, $y$ to Bob). Each player must return an output bit ($a$ from Alice, $b$ from Bob). They win the round if their outputs satisfy:
That is, their outputs XOR to 1 if and only if both inputs are 1. Any classical strategy wins at most 75% of rounds. A quantum strategy using a shared Bell pair wins approximately 85.4% ($\cos^2(\pi/8)$) of rounds — a quantum advantage that directly mirrors the Bell inequality violation.
Game Controls
Win Rate Comparison
10. Source code
Bell state preparation, teleportation, superdense coding, CHSH, and entanglement entropy
// ──── Bell state preparation ────
function prepareBellPhi(q1, q2):
H(q1) // Hadamard on qubit 1
CNOT(q1, q2) // CNOT: q1=control, q2=target
return (q1, q2) // now in |Φ+⟩ = (|00⟩+|11⟩)/√2
// ──── Bell measurement ────
function bellMeasure(q1, q2):
CNOT(q1, q2)
H(q1)
m1 = measure(q1) // classical bit
m2 = measure(q2) // classical bit
return (m1, m2) // 00→Φ+, 01→Ψ+, 10→Φ-, 11→Ψ-
// ──── Quantum teleportation ────
function teleport(psi, q2, q3):
// psi = α|0⟩+β|1⟩, q2 and q3 in |Φ+⟩_{23}
CNOT(psi, q2) // psi is control
H(psi)
(m1, m2) = measure(psi, q2)
send_classical(m1, m2) to Bob
// Bob applies corrections:
if m2 == 1: X(q3)
if m1 == 1: Z(q3)
// q3 is now in state α|0⟩+β|1⟩
// ──── Superdense coding ────
function superdense_send(b1, b2, qubit_A):
if b2 == 1: X(qubit_A)
if b1 == 1: Z(qubit_A)
send_qubit(qubit_A) to Bob
function superdense_receive(qubit_A, qubit_B):
(b1, b2) = bellMeasure(qubit_A, qubit_B)
return (b1, b2)
// ──── Entanglement entropy ────
function entanglement_entropy(state_vector, dim_A, dim_B):
M = reshape(state_vector, (dim_A, dim_B))
(_, singular_vals, _) = svd(M)
lambdas_sq = singular_vals^2
return -sum(l * log2(l) for l in lambdas_sq if l > 0)import numpy as np
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.quantum_info import Statevector
# ──── Bell state preparation (Qiskit) ────
def make_bell_phi_plus():
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
return qc
# ──── Quantum teleportation circuit (Qiskit) ────
def teleportation_circuit(alpha, beta):
"""Teleport |psi⟩ = alpha|0⟩ + beta|1⟩ from q0 to q2."""
q = QuantumRegister(3, 'q')
c = ClassicalRegister(2, 'c')
qc = QuantumCircuit(q, c)
# Prepare the state to teleport on q0
qc.initialize([alpha, beta], 0)
# Create Bell pair between q1 and q2
qc.h(1)
qc.cx(1, 2)
# Alice's Bell measurement
qc.cx(0, 1)
qc.h(0)
qc.measure([0, 1], [0, 1])
# Bob's corrections (classically controlled)
with qc.if_test((c[0], 1)):
qc.z(2)
with qc.if_test((c[1], 1)):
qc.x(2)
return qc
# ──── Entanglement entropy from density matrix (numpy) ────
def entanglement_entropy(state_vec, dim_a, dim_b):
"""
state_vec: (dim_a * dim_b,) complex array
Returns entanglement entropy S(A) in ebits.
"""
M = state_vec.reshape(dim_a, dim_b)
_, singular_vals, _ = np.linalg.svd(M)
lambdas_sq = singular_vals**2
lambdas_sq = lambdas_sq[lambdas_sq > 1e-12] # drop numerical zeros
return -np.sum(lambdas_sq * np.log2(lambdas_sq))
# ──── Example: compute entropy of |Φ+⟩ ────
phi_plus = np.array([1, 0, 0, 1]) / np.sqrt(2)
S = entanglement_entropy(phi_plus, 2, 2)
print(f"Entanglement entropy of |Φ+⟩: {S:.4f} ebit") # → 1.0000
# ──── CHSH expectation value ────
def chsh_expectation(rho, a, a_prime, b, b_prime):
"""
Compute S = E(a,b) - E(a,b') + E(a',b) + E(a',b')
for a density matrix rho on C^2 ⊗ C^2.
Measurement axes given as angles (radians) in the xz-plane.
"""
def observable(angle):
return np.array([[np.cos(angle), np.sin(angle)],
[np.sin(angle), -np.cos(angle)]])
def E(theta_a, theta_b):
A = observable(theta_a)
B = observable(theta_b)
op = np.kron(A, B)
return np.real(np.trace(rho @ op))
S = abs(E(a, b) - E(a, b_prime) + E(a_prime, b) + E(a_prime, b_prime))
return S
# Optimal angles for |Φ+⟩: a=0, a'=π/2, b=π/4, b'=3π/4
rho_phi_plus = np.outer(phi_plus, phi_plus.conj())
S = chsh_expectation(rho_phi_plus, 0, np.pi/2, np.pi/4, 3*np.pi/4)
print(f"CHSH value for |Φ+⟩: {S:.4f} (theoretical 2√2 ≈ {2*np.sqrt(2):.4f})")// ──── Statevector simulator (4-qubit max) ────
// Represent quantum state as array of Complex numbers.
// Complex number: {re, im}
const C = {
add: (a, b) => ({ re: a.re + b.re, im: a.im + b.im }),
mul: (a, b) => ({ re: a.re*b.re - a.im*b.im, im: a.re*b.im + a.im*b.re }),
scale: (a, s) => ({ re: a.re*s, im: a.im*s }),
norm2: (a) => a.re*a.re + a.im*a.im,
};
function zero(n) { return Array.from({length: n}, () => ({re:0, im:0})); }
// ──── Two-qubit statevector teleportation ────
// State vector for 3 qubits: 8 amplitudes, index = q0 q1 q2 (big-endian)
function teleport(alpha, beta) {
// Initial: |psi⟩_0 ⊗ |00⟩_{12}
// |psi⟩ = alpha|0⟩ + beta|1⟩
let sv = zero(8);
sv[0b000] = { re: alpha.re, im: alpha.im }; // alpha|000⟩
sv[0b100] = { re: beta.re, im: beta.im }; // beta|100⟩
// Apply H to q1 (index 1): |0⟩ → (|0⟩+|1⟩)/√2
sv = applyH(sv, 1, 3);
// Apply CNOT q1→q2
sv = applyCNOT(sv, 1, 2, 3);
// Bell pair created on q1,q2
// Alice: CNOT q0→q1
sv = applyCNOT(sv, 0, 1, 3);
// Alice: H on q0
sv = applyH(sv, 0, 3);
// Measure q0 and q1, get outcomes m0, m1
const [m0, sv1] = measure(sv, 0, 3);
const [m1, sv2] = measure(sv1, 1, 3);
// Bob applies corrections
let svFinal = sv2;
if (m1 === 1) svFinal = applyX(svFinal, 2, 3);
if (m0 === 1) svFinal = applyZ(svFinal, 2, 3);
// Extract q2 state (|0⟩ and |1⟩ amplitudes)
const amp0 = svFinal[0b000]; // q2=0 outcome (q0=q1=0)
const amp1 = svFinal[0b001]; // q2=1 outcome
return { amp0, amp1 }; // should equal { alpha, beta }
}
function applyH(sv, qubit, nQubits) {
const out = zero(sv.length);
const s = 1 / Math.sqrt(2);
for (let i = 0; i < sv.length; i++) {
const bit = (i >> (nQubits - 1 - qubit)) & 1;
const j = i ^ (1 << (nQubits - 1 - qubit));
if (bit === 0) {
out[i] = C.add(out[i], C.scale(sv[i], s));
out[j] = C.add(out[j], C.scale(sv[i], s));
} else {
out[i] = C.add(out[i], C.scale(sv[j], -s));
}
}
return out;
}
function applyCNOT(sv, ctrl, tgt, nQubits) {
const out = zero(sv.length);
for (let i = 0; i < sv.length; i++) {
const ctrlBit = (i >> (nQubits - 1 - ctrl)) & 1;
if (ctrlBit === 1) {
const j = i ^ (1 << (nQubits - 1 - tgt));
out[j] = C.add(out[j], sv[i]);
} else {
out[i] = C.add(out[i], sv[i]);
}
}
return out;
}
function measure(sv, qubit, nQubits) {
let p0 = 0;
for (let i = 0; i < sv.length; i++) {
if (((i >> (nQubits - 1 - qubit)) & 1) === 0) p0 += C.norm2(sv[i]);
}
const outcome = Math.random() < p0 ? 0 : 1;
const norm = Math.sqrt(outcome === 0 ? p0 : 1 - p0);
const out = zero(sv.length);
for (let i = 0; i < sv.length; i++) {
if (((i >> (nQubits - 1 - qubit)) & 1) === outcome) {
out[i] = C.scale(sv[i], 1 / norm);
}
}
return [outcome, out];
}
function applyX(sv, qubit, nQubits) {
const out = zero(sv.length);
for (let i = 0; i < sv.length; i++) {
const j = i ^ (1 << (nQubits - 1 - qubit));
out[j] = C.add(out[j], sv[i]);
}
return out;
}
function applyZ(sv, qubit, nQubits) {
const out = sv.slice();
for (let i = 0; i < sv.length; i++) {
if (((i >> (nQubits - 1 - qubit)) & 1) === 1) {
out[i] = C.scale(sv[i], -1);
}
}
return out;
}
// ──── Entanglement entropy ────
function entanglementEntropy(sv, dimA) {
const dimB = sv.length / dimA;
// Build matrix M[i][j] = sv[i*dimB + j]
const M = Array.from({length: dimA}, (_, i) =>
Array.from({length: dimB}, (_, j) => sv[i * dimB + j]));
// Compute singular values via SVD approximation (power iteration for 2x2)
if (dimA === 2 && dimB === 2) {
const a = M[0][0], b = M[0][1], c = M[1][0], d = M[1][1];
const det = Math.sqrt(
Math.pow(C.norm2(a) + C.norm2(b) - C.norm2(c) - C.norm2(d), 2) / 4 +
C.norm2(C.add(C.mul(a, {re:c.re,im:-c.im}), C.mul(b, {re:d.re,im:-d.im})))
);
const trace = (C.norm2(a) + C.norm2(b) + C.norm2(c) + C.norm2(d)) / 2;
const s1sq = trace + det, s2sq = trace - det;
const lambdas = [s1sq, s2sq].filter(l => l > 1e-12);
return -lambdas.reduce((s, l) => s + l * Math.log2(l), 0);
}
return NaN;
}
// Example
const phiPlus = [{re:1/Math.sqrt(2),im:0},{re:0,im:0},{re:0,im:0},{re:1/Math.sqrt(2),im:0}];
console.log("Entanglement entropy of |Φ+⟩:", entanglementEntropy(phiPlus, 2));#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <complex.h>
#define N_QUBITS 3
#define DIM (1 << N_QUBITS) /* 8 */
typedef double complex cx;
static cx sv[DIM];
static void apply_h(int qubit) {
const double s = 1.0 / sqrt(2.0);
cx tmp[DIM] = {0};
for (int i = 0; i < DIM; i++) {
int bit = (i >> (N_QUBITS - 1 - qubit)) & 1;
int j = i ^ (1 << (N_QUBITS - 1 - qubit));
if (bit == 0) { tmp[i] += s * sv[i]; tmp[j] += s * sv[i]; }
else { tmp[i] -= s * sv[j]; }
}
for (int i = 0; i < DIM; i++) sv[i] = tmp[i];
}
static void apply_cnot(int ctrl, int tgt) {
cx tmp[DIM] = {0};
for (int i = 0; i < DIM; i++) {
int cb = (i >> (N_QUBITS - 1 - ctrl)) & 1;
if (cb) {
int j = i ^ (1 << (N_QUBITS - 1 - tgt));
tmp[j] += sv[i];
} else {
tmp[i] += sv[i];
}
}
for (int i = 0; i < DIM; i++) sv[i] = tmp[i];
}
static void apply_x(int qubit) {
cx tmp[DIM] = {0};
for (int i = 0; i < DIM; i++) {
int j = i ^ (1 << (N_QUBITS - 1 - qubit));
tmp[j] += sv[i];
}
for (int i = 0; i < DIM; i++) sv[i] = tmp[i];
}
static void apply_z(int qubit) {
for (int i = 0; i < DIM; i++) {
int bit = (i >> (N_QUBITS - 1 - qubit)) & 1;
if (bit) sv[i] = -sv[i];
}
}
/* Measure qubit; return 0 or 1; collapse state */
static int measure_qubit(int qubit) {
double p0 = 0.0;
for (int i = 0; i < DIM; i++) {
int bit = (i >> (N_QUBITS - 1 - qubit)) & 1;
if (bit == 0) p0 += creal(sv[i]) * creal(sv[i]) + cimag(sv[i]) * cimag(sv[i]);
}
int outcome = ((double)rand() / RAND_MAX) < p0 ? 0 : 1;
double norm = sqrt(outcome == 0 ? p0 : 1.0 - p0);
for (int i = 0; i < DIM; i++) {
int bit = (i >> (N_QUBITS - 1 - qubit)) & 1;
if (bit != outcome) sv[i] = 0.0;
else sv[i] /= norm;
}
return outcome;
}
/* Teleport |psi⟩ = alpha|0⟩ + beta|1⟩ encoded in qubit 0.
Bell pair pre-shared on qubits 1 and 2. */
void teleport(cx alpha, cx beta) {
for (int i = 0; i < DIM; i++) sv[i] = 0.0;
sv[0b000] = alpha;
sv[0b100] = beta;
/* Create Bell pair on q1,q2 */
apply_h(1);
apply_cnot(1, 2);
/* Alice: Bell measurement on q0,q1 */
apply_cnot(0, 1);
apply_h(0);
int m0 = measure_qubit(0);
int m1 = measure_qubit(1);
/* Bob: apply corrections */
if (m1) apply_x(2);
if (m0) apply_z(2);
/* Print Bob's qubit amplitudes */
printf("Bob |0⟩ amp: (%.4f, %.4f)\n", creal(sv[0b000]), cimag(sv[0b000]));
printf("Bob |1⟩ amp: (%.4f, %.4f)\n", creal(sv[0b001]), cimag(sv[0b001]));
printf("Expected alpha=(%.4f,%.4f) beta=(%.4f,%.4f)\n",
creal(alpha), cimag(alpha), creal(beta), cimag(beta));
}
int main(void) {
srand(42);
cx alpha = 0.6 + 0.0 * I;
cx beta = 0.8 + 0.0 * I;
teleport(alpha, beta);
return 0;
}#include <iostream>
#include <vector>
#include <complex>
#include <cmath>
#include <cstdlib>
#include <numeric>
#include <algorithm>
using cx = std::complex<double>;
class QuantumState {
public:
int nQubits;
int dim;
std::vector<cx> sv;
explicit QuantumState(int n) : nQubits(n), dim(1 << n), sv(1 << n, cx(0, 0)) {}
void setBasis(int idx, cx amp) { sv[idx] = amp; }
void applyH(int q) {
const double s = 1.0 / std::sqrt(2.0);
std::vector<cx> tmp(dim, cx(0, 0));
for (int i = 0; i < dim; ++i) {
int bit = (i >> (nQubits - 1 - q)) & 1;
int j = i ^ (1 << (nQubits - 1 - q));
if (bit == 0) { tmp[i] += s * sv[i]; tmp[j] += s * sv[i]; }
else { tmp[i] -= s * sv[j]; }
}
sv = tmp;
}
void applyCNOT(int ctrl, int tgt) {
std::vector<cx> tmp(dim, cx(0, 0));
for (int i = 0; i < dim; ++i) {
int cb = (i >> (nQubits - 1 - ctrl)) & 1;
if (cb) tmp[i ^ (1 << (nQubits - 1 - tgt))] += sv[i];
else tmp[i] += sv[i];
}
sv = tmp;
}
void applyX(int q) {
std::vector<cx> tmp(dim, cx(0, 0));
for (int i = 0; i < dim; ++i)
tmp[i ^ (1 << (nQubits - 1 - q))] += sv[i];
sv = tmp;
}
void applyZ(int q) {
for (int i = 0; i < dim; ++i)
if ((i >> (nQubits - 1 - q)) & 1) sv[i] = -sv[i];
}
int measure(int q) {
double p0 = 0.0;
for (int i = 0; i < dim; ++i)
if (!((i >> (nQubits - 1 - q)) & 1)) p0 += std::norm(sv[i]);
int outcome = (static_cast<double>(rand()) / RAND_MAX) < p0 ? 0 : 1;
double norm = std::sqrt(outcome == 0 ? p0 : 1.0 - p0);
for (int i = 0; i < dim; ++i) {
if (((i >> (nQubits - 1 - q)) & 1) != outcome) sv[i] = 0.0;
else sv[i] /= norm;
}
return outcome;
}
// Entanglement entropy via SVD on 2-qubit (4-dim) state
double entanglementEntropy(int dimA) const {
int dimB = dim / dimA;
// Build dimA x dimB matrix
std::vector<std::vector<cx>> M(dimA, std::vector<cx>(dimB));
for (int i = 0; i < dimA; ++i)
for (int j = 0; j < dimB; ++j)
M[i][j] = sv[i * dimB + j];
// For 2x2 case: SVD analytically
if (dimA == 2 && dimB == 2) {
double a2 = std::norm(M[0][0]), b2 = std::norm(M[0][1]);
double c2 = std::norm(M[1][0]), d2 = std::norm(M[1][1]);
cx ab_cd = M[0][0] * std::conj(M[1][0]) + M[0][1] * std::conj(M[1][1]);
double trace = (a2 + b2 + c2 + d2) / 2;
double det = std::sqrt(std::pow((a2 + b2 - c2 - d2) / 2, 2) + std::norm(ab_cd));
double s1sq = trace + det, s2sq = trace - det;
double S = 0;
for (double l : {s1sq, s2sq})
if (l > 1e-12) S -= l * std::log2(l);
return S;
}
return -1.0; // general case not implemented
}
};
int main() {
srand(42);
QuantumState qs(3);
cx alpha(0.6, 0.0), beta(0.8, 0.0);
qs.setBasis(0b000, alpha);
qs.setBasis(0b100, beta);
qs.applyH(1);
qs.applyCNOT(1, 2);
qs.applyCNOT(0, 1);
qs.applyH(0);
int m0 = qs.measure(0);
int m1 = qs.measure(1);
if (m1) qs.applyX(2);
if (m0) qs.applyZ(2);
std::cout << "Teleportation result:" << std::endl;
std::cout << " Bob |0⟩ amp: " << qs.sv[0] << std::endl;
std::cout << " Bob |1⟩ amp: " << qs.sv[1] << std::endl;
std::cout << " Expected alpha=" << alpha << " beta=" << beta << std::endl;
// Entanglement entropy of |Φ+⟩
QuantumState bell(2);
bell.setBasis(0b00, cx(1.0/std::sqrt(2.0), 0));
bell.setBasis(0b11, cx(1.0/std::sqrt(2.0), 0));
std::cout << "Entanglement entropy of |Φ+⟩: " << bell.entanglementEntropy(2) << " ebit" << std::endl;
return 0;
}import java.util.Arrays;
import java.util.Random;
public class QuantumEntanglement {
static final int N = 3; // number of qubits
static final int DIM = 1 << N; // 8
static final double[] re = new double[DIM];
static final double[] im = new double[DIM];
static final Random rng = new Random(42);
static void applyH(int q) {
double s = 1.0 / Math.sqrt(2.0);
double[] tmpRe = new double[DIM], tmpIm = new double[DIM];
for (int i = 0; i < DIM; i++) {
int bit = (i >> (N - 1 - q)) & 1;
int j = i ^ (1 << (N - 1 - q));
if (bit == 0) {
tmpRe[i] += s * re[i]; tmpIm[i] += s * im[i];
tmpRe[j] += s * re[i]; tmpIm[j] += s * im[i];
} else {
tmpRe[i] -= s * re[j]; tmpIm[i] -= s * im[j];
}
}
System.arraycopy(tmpRe, 0, re, 0, DIM);
System.arraycopy(tmpIm, 0, im, 0, DIM);
}
static void applyCNOT(int ctrl, int tgt) {
double[] tmpRe = new double[DIM], tmpIm = new double[DIM];
for (int i = 0; i < DIM; i++) {
int cb = (i >> (N - 1 - ctrl)) & 1;
if (cb == 1) {
int j = i ^ (1 << (N - 1 - tgt));
tmpRe[j] += re[i]; tmpIm[j] += im[i];
} else {
tmpRe[i] += re[i]; tmpIm[i] += im[i];
}
}
System.arraycopy(tmpRe, 0, re, 0, DIM);
System.arraycopy(tmpIm, 0, im, 0, DIM);
}
static void applyX(int q) {
double[] tmpRe = new double[DIM], tmpIm = new double[DIM];
for (int i = 0; i < DIM; i++) {
int j = i ^ (1 << (N - 1 - q));
tmpRe[j] += re[i]; tmpIm[j] += im[i];
}
System.arraycopy(tmpRe, 0, re, 0, DIM);
System.arraycopy(tmpIm, 0, im, 0, DIM);
}
static void applyZ(int q) {
for (int i = 0; i < DIM; i++)
if (((i >> (N - 1 - q)) & 1) == 1) { re[i] = -re[i]; im[i] = -im[i]; }
}
static int measure(int q) {
double p0 = 0.0;
for (int i = 0; i < DIM; i++)
if (((i >> (N - 1 - q)) & 1) == 0) p0 += re[i]*re[i] + im[i]*im[i];
int outcome = rng.nextDouble() < p0 ? 0 : 1;
double norm = Math.sqrt(outcome == 0 ? p0 : 1.0 - p0);
for (int i = 0; i < DIM; i++) {
if (((i >> (N - 1 - q)) & 1) != outcome) { re[i] = 0; im[i] = 0; }
else { re[i] /= norm; im[i] /= norm; }
}
return outcome;
}
public static void main(String[] args) {
double alpha = 0.6, beta = 0.8;
Arrays.fill(re, 0.0); Arrays.fill(im, 0.0);
re[0b000] = alpha; re[0b100] = beta;
applyH(1); applyCNOT(1, 2); // Bell pair on q1,q2
applyCNOT(0, 1); applyH(0); // Alice's Bell measurement
int m0 = measure(0), m1 = measure(1);
if (m1 == 1) applyX(2);
if (m0 == 1) applyZ(2);
System.out.printf("Bob |0⟩: (%.4f, %.4f) expected alpha=%.4f%n",
re[0b000], im[0b000], alpha);
System.out.printf("Bob |1⟩: (%.4f, %.4f) expected beta=%.4f%n",
re[0b001], im[0b001], beta);
}
}package main
import (
"fmt"
"math"
"math/cmplx"
"math/rand"
)
const nQubits = 3
const dim = 1 << nQubits // 8
var sv [dim]complex128
func applyH(q int) {
s := 1.0 / math.Sqrt(2.0)
var tmp [dim]complex128
for i := 0; i < dim; i++ {
bit := (i >> (nQubits - 1 - q)) & 1
j := i ^ (1 << (nQubits - 1 - q))
if bit == 0 {
tmp[i] += complex(s, 0) * sv[i]
tmp[j] += complex(s, 0) * sv[i]
} else {
tmp[i] -= complex(s, 0) * sv[j]
}
}
sv = tmp
}
func applyCNOT(ctrl, tgt int) {
var tmp [dim]complex128
for i := 0; i < dim; i++ {
cb := (i >> (nQubits - 1 - ctrl)) & 1
if cb == 1 {
j := i ^ (1 << (nQubits - 1 - tgt))
tmp[j] += sv[i]
} else {
tmp[i] += sv[i]
}
}
sv = tmp
}
func applyX(q int) {
var tmp [dim]complex128
for i := 0; i < dim; i++ {
j := i ^ (1 << (nQubits - 1 - q))
tmp[j] += sv[i]
}
sv = tmp
}
func applyZ(q int) {
for i := 0; i < dim; i++ {
if (i>>(nQubits-1-q))&1 == 1 {
sv[i] = -sv[i]
}
}
}
func measureQubit(q int) int {
p0 := 0.0
for i := 0; i < dim; i++ {
if (i>>(nQubits-1-q))&1 == 0 {
p0 += math.Pow(real(sv[i]), 2) + math.Pow(imag(sv[i]), 2)
}
}
outcome := 0
if rand.Float64() >= p0 {
outcome = 1
}
norm := math.Sqrt(map[int]float64{0: p0, 1: 1.0 - p0}[outcome])
for i := 0; i < dim; i++ {
if (i>>(nQubits-1-q))&1 != outcome {
sv[i] = 0
} else {
sv[i] /= complex(norm, 0)
}
}
return outcome
}
// entanglementEntropy computes S(A) for a 2-qubit state (dim=4).
func entanglementEntropy(state [4]complex128) float64 {
// M = [[m00, m01],[m10, m11]]
a, b, c, d := state[0], state[1], state[2], state[3]
trace := (cmplx.Abs(a)*cmplx.Abs(a) + cmplx.Abs(b)*cmplx.Abs(b) +
cmplx.Abs(c)*cmplx.Abs(c) + cmplx.Abs(d)*cmplx.Abs(d)) / 2
ab_cd := a*cmplx.Conj(c) + b*cmplx.Conj(d)
offDiag := real(ab_cd)*real(ab_cd) + imag(ab_cd)*imag(ab_cd)
rowDiff := (cmplx.Abs(a)*cmplx.Abs(a) + cmplx.Abs(b)*cmplx.Abs(b) -
cmplx.Abs(c)*cmplx.Abs(c) - cmplx.Abs(d)*cmplx.Abs(d)) / 2
det := math.Sqrt(rowDiff*rowDiff + offDiag)
S := 0.0
for _, lsq := range []float64{trace + det, trace - det} {
if lsq > 1e-12 {
S -= lsq * math.Log2(lsq)
}
}
return S
}
func main() {
rand.Seed(42)
alpha := complex(0.6, 0)
beta := complex(0.8, 0)
for i := range sv {
sv[i] = 0
}
sv[0b000] = alpha
sv[0b100] = beta
applyH(1)
applyCNOT(1, 2)
applyCNOT(0, 1)
applyH(0)
m0 := measureQubit(0)
m1 := measureQubit(1)
if m1 == 1 {
applyX(2)
}
if m0 == 1 {
applyZ(2)
}
fmt.Printf("Bob |0⟩ amp: (%.4f, %.4f) expected alpha=(%.4f, 0)\n",
real(sv[0b000]), imag(sv[0b000]), real(alpha))
fmt.Printf("Bob |1⟩ amp: (%.4f, %.4f) expected beta=(%.4f, 0)\n",
real(sv[0b001]), imag(sv[0b001]), real(beta))
bellState := [4]complex128{1 / complex(math.Sqrt(2), 0), 0, 0, 1 / complex(math.Sqrt(2), 0)}
fmt.Printf("Entanglement entropy of |Φ+⟩: %.4f ebit\n", entanglementEntropy(bellState))
}11. Summary
Quantum entanglement is not a curiosity or a philosophical puzzle — it is the core resource that makes quantum information processing qualitatively different from its classical counterpart. Here is what to carry forward:
| Concept | Key result | Why it matters |
|---|---|---|
| Entanglement definition | $|\psi\rangle \neq |\psi_A\rangle \otimes |\psi_B\rangle$; Schmidt rank $> 1$ | Foundation — knowing when states are entangled |
| Bell states | Four maximally entangled 2-qubit states; 1 ebit each | The "hydrogen atom" of quantum information: minimal, maximally useful |
| Bell's theorem / CHSH | $S_{\text{quantum}} = 2\sqrt{2} > 2 = S_{\text{LHV}}$ | Rules out local hidden variables; entanglement is real and non-classical |
| Quantum teleportation | 1 ebit + 2 cbits → transfer of 1 qubit state | Shows entanglement is a fungible resource; underpins quantum networking |
| Superdense coding | 1 ebit + 1 qubit → 2 classical bits | Dual of teleportation; doubles classical channel capacity with shared entanglement |
| Entanglement entropy | $S(A) = -\operatorname{tr}(\rho_A \log_2 \rho_A)$; equals Schmidt entropy | Quantifies entanglement; determines classical simulation hardness |
| GHZ vs W states | Inequivalent LOCC classes for 3+ qubits | Demonstrates richness of multipartite entanglement; different practical uses |
| Resource theory | Entanglement + magic (T gates) = universal QC; entanglement alone ≠ speedup | Explains why Clifford circuits, which create entanglement, are still classically simulatable |
Entanglement is the quantum analogue of classical correlation, but it is strictly stronger: it cannot be created by any local operations (not even probabilistically), it cannot be explained by any pre-agreed strategy, and it enables tasks — teleportation, superdense coding, quantum key distribution — that are classically impossible. It is the fuel in the quantum advantage engine. The practical challenge for quantum computing is not creating entanglement (two gates suffice) but preserving it: decoherence destroys entanglement on microsecond timescales in current hardware, and scaling to the entanglement depths needed for useful computation is the central engineering challenge of the decade.