Note
Go to the end to download the full example code.
Information rates of QAM#
The mutual information \(I(X; Y)\) of a constellation over AWGN is the rate a receiver that decodes symbols can achieve; the generalised mutual information (GMI) is the rate of a bit-interleaved receiver that decodes bits independently, the usual coded-modulation receiver. Both stay below the Shannon capacity \(\log_2(1 + \mathrm{SNR})\) and saturate at \(\log_2 M\); with Gray labels the gap between them is small.
The curves are computed by Gauss–Hermite quadrature (comnum.theory_mi(),
comnum.theory_gmi()); the markers are Monte Carlo estimates on 100 000 noisy
16-QAM symbols (comnum.information_rates()).
import matplotlib.pyplot as plt
import numpy as np
import comnum
from comnum.plot import BACKGROUND, GREY, ORANGE
snr = np.arange(-5.0, 30.5, 1.0)
fig, ax = plt.subplots()
ax.plot(snr, np.log2(1 + comnum.db2lin(snr)), color=GREY, linewidth=1.4, label="Shannon capacity")
for m in [4, 16, 64]:
mi = [comnum.theory_mi(m, s, "qam") for s in snr]
gmi = [comnum.theory_gmi(m, s, "qam") for s in snr]
(line,) = ax.plot(snr, mi, label=f"{m}-QAM, MI")
ax.plot(snr, gmi, color=line.get_color(), linestyle="--", linewidth=1.4, label=f"{m}-QAM, GMI")
# Monte Carlo estimates for 16-QAM
c = comnum.Constellation("qam", 16).normalized()
est = []
for i, s in enumerate(snr[::3]):
rng = comnum.Rng.stream(11, "mi", i, 0)
idx, x = comnum.symbols(c, 100_000, rng)
est.append(comnum.information_rates(comnum.awgn(x, s, rng), idx, c))
ax.plot(
snr[::3],
[e["mi"] for e in est],
"o",
color=ORANGE,
markeredgecolor=BACKGROUND,
label="16-QAM, MI simulated",
)
ax.set(
xlabel="SNR $E_s/N_0$ (dB)",
ylabel="bits per symbol",
xlim=(-5, 30),
ylim=(0, 7),
title="Information rates over AWGN",
)
ax.legend(ncol=2, loc="lower right")
fig.tight_layout()

The gap to capacity at 3.5 bits per symbol, which needs an SNR of 10 log10(2^3.5 - 1) = 10.13 dB: 16-QAM, close to saturation, is 1.4 dB away; 64-QAM 0.7 dB (what remains is the shaping gap of uniform square constellations).
16-QAM reaches 3.5 bits/symbol at 11.57 dB (capacity: 10.13 dB)
64-QAM reaches 3.5 bits/symbol at 10.84 dB (capacity: 10.13 dB)
Total running time of the script: (0 minutes 15.992 seconds)