PAPR reduction techniques#

The classical techniques (Han & Lee, IEEE Wireless Commun. 12(2), 2005), applied to 16-QAM OFDM with 128 subcarriers (oversampling by 4):

Without distortion — the data are sent unchanged, at the price of side information or rate:

  • selected mapping (SLM): the best of \(U\) phase-rotated copies (Bäuml, Fischer & Huber, Electron. Lett. 32(22), 1996); with independent candidates the CCDF is that of OFDM raised to the power \(U\) (dashed);

  • partial transmit sequences (PTS): the best phase combination of \(V\) subblocks (Müller & Huber, Electron. Lett. 33(5), 1997);

  • tone reservation (TR): a peak-cancelling signal on unused subcarriers (Tellado, Multicarrier Modulation with Low PAR, 2000).

With distortion — the signal itself is modified:

  • clipping and filtering (ICF): repeated clipping and removal of the out-of-band components (Armstrong, Electron. Lett. 38(5), 2002);

  • active constellation extension (ACE): outer points pushed outwards, where they do not harm the decisions (Krongold & Jones, IEEE Trans. Broadcast. 49(3), 2003);

  • companding: μ-law (Wang, Tjhung & Ng, IEEE Trans. Broadcast. 45(3), 1999) or exponential (Jiang, Yang & Song, IEEE Trans. Broadcast. 51(2), 2005), expanded at the receiver.

import matplotlib.pyplot as plt
import numpy as np

import comnum
from comnum.plot import BACKGROUND, GREY

N, BLOCKS = 128, 1500
ofdm = comnum.Ofdm(N, N)
c = comnum.Constellation("qam", 16).normalized()
_, d = comnum.symbols(c, N * BLOCKS, comnum.Rng(1))
t = np.arange(2.0, 12.1, 0.1)
original = comnum.ccdf(ofdm.papr_db(ofdm.modulate(d)), t)


def ccdf_of(reduction):
    k = ofdm.data_len(reduction)
    x, _ = ofdm.modulate_with(d[: (d.size // k) * k], reduction)
    return comnum.ccdf(ofdm.papr_db(x), t)


fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9.5, 4.0), sharey=True)
for ax in (ax1, ax2):
    ax.semilogy(t, original, color=GREY, linewidth=1.6, label="OFDM")
lossless = {
    "SLM, U = 16": comnum.PaprReduction.slm(16),
    "PTS, V = 4, W = 4": comnum.PaprReduction.pts(4, phases=4),
    "TR, 12 tones": comnum.PaprReduction.tone_reservation(N, 12, 4.0),
}
for name, r in lossless.items():
    (line,) = ax1.semilogy(t, ccdf_of(r), label=name)
    if name.startswith("SLM"):
        ax1.semilogy(
            t,
            comnum.theory_papr_ccdf(N, t, candidates=16),
            color=line.get_color(),
            linestyle="--",
            linewidth=1.2,
            label="SLM, theory",
        )
ax1.set(
    xlabel=r"$\gamma$ (dB)",
    ylabel=r"$\Pr(\mathrm{PAPR} > \gamma)$",
    ylim=(1e-3, 1.1),
    xlim=(2, 12),
    title="without distortion",
)
ax1.legend(loc="lower left")
distorting = {
    "ICF, CR = 4 dB, 4 it.": comnum.PaprReduction.clip_filter(4.0, iterations=4),
    "ACE, 10 it.": comnum.PaprReduction.ace(c, 4.0, iterations=10),
    "exponential companding": comnum.PaprReduction.exponential(2.0),
}
for name, r in distorting.items():
    ax2.semilogy(t, ccdf_of(r), label=name)
ax2.set(xlabel=r"$\gamma$ (dB)", xlim=(2, 12), title="with distortion")
ax2.legend(loc="lower left")
fig.tight_layout()
without distortion, with distortion

The price of the distortion: the bit error rate over AWGN at a fixed average transmitted power (the power added by ACE is paid for). ACE keeps the performance of OFDM — its points only move away from the decision boundaries — while clipping and filtering leaves an in-band distortion floor. μ-law companding is expanded at the receiver. The exponential law is not: it squeezes every amplitude above about twice the RMS into a narrow range, where the expansion would amplify the noise 20 to 150 times; treated as a distortion (Bussgang gain), it leaves an error floor instead.

def ber_curve(reduction, ebn0s, blocks=400, seed=2, expand=True):
    k = ofdm.data_len(reduction)
    bits = comnum.bits(4 * k * blocks, comnum.Rng(seed))
    s = c.map_bits(bits)
    x, side = ofdm.modulate_with(s, reduction)
    p = np.mean(np.abs(x) ** 2)
    out = []
    for i, eb in enumerate(ebn0s):
        # Eb/N0 = (block energy / bits) / σ²: the block energy p·L·N carries 4 k bits
        s2 = p * ofdm.fft_len / (4 * k * comnum.db2lin(eb))
        y = x + comnum.Rng.stream(seed, "awgn", i, 0).complex_noise(x.size, s2)
        if expand:
            z = ofdm.demodulate_with(y, reduction, side=side)
        else:  # no expansion: one-tap equalisation by the Bussgang gain
            z = ofdm.demodulate(y)
            z = z * np.vdot(s, s) / np.vdot(s, z)
        out.append(comnum.ber(c.demap_bits(z), bits))
    return np.array(out)


ebn0 = np.arange(0.0, 17.0, 2.0)
fig, ax = plt.subplots()
eb = np.linspace(0, 16, 161)
ax.semilogy(
    eb,
    comnum.theory_ber(16, eb, "qam"),
    color=GREY,
    linewidth=1.4,
    label="16-QAM, AWGN",
)
cases = {
    "ICF, CR = 4 dB": comnum.PaprReduction.clip_filter(4.0, iterations=4),
    "ACE": comnum.PaprReduction.ace(c, 4.0, iterations=10),
    "μ-law, μ = 4": comnum.PaprReduction.mu_law(4.0),
    "exponential, not expanded": comnum.PaprReduction.exponential(2.0),
}
for (name, r), marker in zip(cases.items(), ["o", "s", "D", "^"], strict=True):
    b = ber_curve(r, ebn0, expand="not expanded" not in name)
    keep = b > 0
    ax.semilogy(ebn0[keep], b[keep], marker=marker, markeredgecolor=BACKGROUND, label=name)
ax.set(
    ylim=(1e-5, 0.5),
    xlim=(0, 16),
    xlabel="$E_b/N_0$ (dB)",
    ylabel="bit error rate",
    title="16-QAM OFDM over AWGN: the cost of the distortion",
)
ax.legend()
fig.tight_layout()
16-QAM OFDM over AWGN: the cost of the distortion

Total running time of the script: (0 minutes 1.673 seconds)

Gallery generated by Sphinx-Gallery