Note
Go to the end to download the full example code.
5G NR channel codes: LDPC and polar#
The data channels of 5G NR use a quasi-cyclic LDPC code, the control channels a polar
code (3GPP TS 38.212; T. Richardson and S. Kudekar, “Design of low-density parity check
codes for 5G new radio,” IEEE Commun. Mag. 56(3), 2018; V. Bioglio, C. Condo and
I. Land, “Design of polar codes in 5G New Radio,” IEEE Commun. Surveys Tuts. 23(1),
2021). comnum.NrLdpc and comnum.NrPolar build them from the tables of
the standard — the two LDPC base graphs, the polar sequence of 1024 positions, the
interleavers — with their rate matching to any number of transmitted bits.
Their encoders give the same bits as the reference vectors of Sionna
(tests/oracle/test_sionna_vectors.py): the LDPC codewords of 28 sizes from 64 to 8448
information bits, both base graphs, and the polar codewords of the uplink control
information with puncturing, shortening and repetition.
The LDPC code: base graphs and lifting#
Base graph 1 (46 × 68) serves large blocks and high rates, base graph 2 (42 × 52) small blocks and low rates (TS 38.212, Sec. 7.2.2); every nonzero entry becomes a \(Z \times Z\) cyclically shifted identity, \(Z\) the smallest lifting size that holds the block. The first \(2Z\) systematic bits are never sent, and the circular buffer gives any rate.
for k, n in [(100, 300), (1000, 1500), (4000, 4800), (8448, 25344)]:
code = comnum.NrLdpc(k, n)
print(f"K' = {k:5d}, E = {n:5d}: base graph {code.base_graph}, Z = {code.lifting_size}")
K' = 100, E = 300: base graph 2, Z = 18
K' = 1000, E = 1500: base graph 2, Z = 104
K' = 4000, E = 4800: base graph 1, Z = 192
K' = 8448, E = 25344: base graph 1, Z = 384
Block error rates over BPSK and AWGN#
Belief propagation (50 iterations of sum-product) for the LDPC code, CRC-aided SC
list (L = 8) for the polar code, against the normal approximation of the best code of
the same length and rate (Y. Polyanskiy, H. V. Poor and S. Verdú, IEEE Trans. Inf.
Theory 56(5), 2010; comnum.theory_normal_approximation(), dashed). At a block error
rate of 10⁻², the LDPC code of 500 bits at rate 1/2 sits about 0.6 dB from it, the
polar code of 40 bits in 128 about 1.1 dB, part of which is its CRC
(validation/tests/coding/nr_codes.rs).
rng = comnum.Rng(31)
def bler(code, ebn0_db, blocks):
b = comnum.bits(code.k * blocks, rng)
errors = bpsk_awgn_decisions(code, b, ebn0_db, rng) != b
return np.mean(np.any(errors.reshape(blocks, code.k), axis=1))
cases = [
("LDPC, K' = 500, E = 1000", comnum.NrLdpc(500, 1000), np.arange(0.5, 2.01, 0.5), BLUE),
("LDPC, K' = 500, E = 2000", comnum.NrLdpc(500, 2000), np.arange(-0.5, 1.01, 0.5), TEAL),
(
"polar uplink, A = 40, E = 128",
comnum.NrPolar(40, 128, list_size=8),
np.arange(0.5, 3.51, 0.5),
CRIMSON,
),
]
fig, ax = plt.subplots(figsize=(6.4, 4.0))
for name, code, grid, color in cases:
p = [bler(code, e, 250) for e in grid]
ax.semilogy(grid, np.maximum(p, 1e-3), "o-", color=color, markersize=4, label=name)
fine = np.linspace(grid[0] - 1, grid[-1], 60)
ax.semilogy(
fine,
comnum.theory_normal_approximation(code.n, code.k, fine),
"--",
color=color,
linewidth=1,
)
ax.set(xlabel="$E_b/N_0$ (dB)", ylabel="block error rate", ylim=(3e-3, 1.1))
ax.grid(alpha=0.3, which="both")
ax.legend(fontsize=8)
fig.tight_layout()

Rate matching of the polar code#
The mother code length is a power of two; the sub-block interleaver and the bit selection reach any \(E\): the last \(E\) bits (puncturing) at low rate, the first \(E\) (shortening) at high rate, the word repeated when \(E \ge N\).
A = 30, E = 45: N = 64, shortening
A = 30, E = 100: N = 128, puncturing
A = 30, E = 200: N = 256, puncturing
A = 100, E = 1500: N = 1024, repetition
Total running time of the script: (0 minutes 15.289 seconds)