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()
Information rates over AWGN

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).

target = 3.5
for m in [16, 64]:
    lo, hi = 0.0, 30.0
    for _ in range(50):
        mid = (lo + hi) / 2
        lo, hi = (lo, mid) if comnum.theory_mi(m, mid, "qam") > target else (mid, hi)
    print(
        f"{m}-QAM reaches {target} bits/symbol at {lo:.2f} dB (capacity: {comnum.lin2db(2**target - 1):.2f} dB)"
    )
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)

Gallery generated by Sphinx-Gallery