Low Density Parity Check (LDPC) Codes in 5G

5G networks uses low density parity check codes (ldpc) for forward error correction of shared channels used to communicate user data over air interface. These channel codes:

  • achieve channel capacity for large block lengths

  • extremely robust against noise

  • scalable and efficient hardware implementation.

  • Low power consumption and silicon footprint.

  • Can be enhanced to support diverse payload sizes and code-rates.

  • easy to design and implement the low complexity decoder.

  • capable of considering both reliability and high data rates.

The table of content of this tutorial is illustrated below:

Import Libraries

Python LIbraries

[1]:
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'

# %matplotlib widget
import matplotlib.pyplot as plt
import matplotlib as mpl
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset

import numpy as np

5G Toolkit Libraries

[2]:
from toolkit5G.SymbolMapping import Demapper
from toolkit5G.SymbolMapping import Mapper
from toolkit5G.ChannelCoder  import LDPCEncoder5G
from toolkit5G.ChannelCoder  import LDPCDecoder5G
from toolkit5G.ChannelCoder.LDPC  import LDPCparameters
from toolkit5G.Ratematcher.LDPC  import BitSelection
from toolkit5G.Ratematcher.LDPC  import BitDeselection
[3]:
# from IPython.display import display, HTML
# display(HTML("<style>.container { width:80% !important; }</style>"))

Symbol Mapping Configurations

Symbol mapping/Demapping is performed for:

  • QPSK constellation defined by constellation_type which encodes

  • 1 bit per contellation symbol defined by num_bits_per_symbol.

  • The demapper generates log likelihood ratios configured by hard_out

  • using a Log-Map (“app”) decoder defined using demapping_method.

The flow of the implementation can be understood using folowing diagram: LDPCCodes

[4]:
constellation_type  = "qam"
num_bits_per_symbol = 2
hard_out            = False
demapping_method    = "app"

mapper  = Mapper(constellation_type, num_bits_per_symbol)
demapper= Demapper(demapping_method, constellation_type,  num_bits_per_symbol, hard_out = hard_out)

Simulation: Variation in Reliability with code-rate for fixed block-length

LDPC Parameters

  • tbSize: Defines the transport block size to be processed by LDPC Channel coder.

  • codeRate: codeRate of LDPC codec.

  • lpdcConfig: LDPC configuration is used to generate the LDPC parameters. as described below.

  • k: Number of information bits to be encoded.

  • bg: Base graph type.

  • zc: Lifting factor size.

  • numCBs: Number of codeblocks.

Note: tbSize and block length are different in LDPC codes. The length of block encoded using LDPC is given by K.

Simulation Procedure

This subsection performs the simulation which involve following steps:

  • Computation of LDPC parameters based on tbSize and codeRate.

  • Bits tensor generation of dimension numBatches \(\times\) numCBs \(\times\) k.

  • LDPC Encoding.

  • Rate matching

    • Bit-Selection

    • Bit-Interleaving

  • QPSK Symbol Mapping of encoded bits.

  • Passing through the AWGN Channel

    • Adding noise to BPSK symbols with a given SNR

  • Symbol De-mapping using Log Map Demapper defined by "app".

    • Generates Log likelihood values as hard_out is set to False.

  • De-rate matching

    • Bit-Deselection

    • Bit-Deinterleaving

  • LDPC Decoding

  • BER computation

The flow of the implementation can be understood using folowing diagram: LDPCCodes

The bit error rate (BER) is computed for every combination of code-rate \(= \{0.33, 0.4, 0.5, 0.67, 0.75, 0.83, 0.89\}\) and SNR \(= \{-5 , -2.5, 0 , 2.5, 5\}\) dB.

[5]:
tbSize     = 32
numBatches = 100

codeRate   = np.array([0.33, 0.5, 0.75, 0.89])
SNRdB      = np.linspace(-5,5,5)
SNR        = 10**(0.1*SNRdB)


BER        = np.zeros((codeRate.size, SNRdB.size))
rIndex     = 0
snrIndex   = 0

for coderate in codeRate:
    lpdcConfig = LDPCparameters(tbSize, coderate)
    k          = lpdcConfig.k_ldpc

    bg         = lpdcConfig.baseGraph
    zc         = lpdcConfig.liftingfactor
    numCBs     = lpdcConfig.numCodeBlocks
    N          = lpdcConfig.n

    bits       = np.float32(np.random.randint(2, size = (numBatches, numCBs, k), dtype=np.int8))
    encoder    = LDPCEncoder5G(k, bg, zc)
    encBits    = encoder(bits)
    E          = int(k/coderate)
    rateMatch  = BitSelection(numLayers=1, modOrder=2, numCodedBits=E, baseGraph=bg, enableLBRM=False, tbSize=tbSize, numCBs=numCBs)
    codeword   = rateMatch(encBits, rvID=0)
    symbols    = mapper(codeword[0])
    snrIndex   = 0
    for snr in SNR:

        symbs      = symbols + np.sqrt(0.5/snr)*(np.random.standard_normal(size=symbols.shape)+1j*np.random.standard_normal(size=symbols.shape)).astype(np.complex64)

        llrs       = demapper([symbs , np.float32(1/snr)])

        rxCodeword = BitDeselection(np.array([]), bg, zc, False)([llrs], rvID=0)
        decoder    = LDPCDecoder5G(bg, zc)
        decBits    = decoder(rxCodeword)
        BER[rIndex, snrIndex] = np.mean(np.abs(decBits-bits))
        print("For coderate="+str(coderate)+" ("+str(k/codeword[0].shape[-1])+"), At SNR(dB): "+str(SNRdB[snrIndex])+" | Bit Error Rate: "+str(BER[rIndex, snrIndex]))
        snrIndex += 1
    rIndex   += 1

For coderate=0.33 (0.3305785123966942), At SNR(dB): -5.0 | Bit Error Rate: 0.31212499737739563
For coderate=0.33 (0.3305785123966942), At SNR(dB): -2.5 | Bit Error Rate: 0.20225000381469727
For coderate=0.33 (0.3305785123966942), At SNR(dB): 0.0 | Bit Error Rate: 0.017000000923871994
For coderate=0.33 (0.3305785123966942), At SNR(dB): 2.5 | Bit Error Rate: 0.0
For coderate=0.33 (0.3305785123966942), At SNR(dB): 5.0 | Bit Error Rate: 0.0
For coderate=0.5 (0.5), At SNR(dB): -5.0 | Bit Error Rate: 0.31687501072883606
For coderate=0.5 (0.5), At SNR(dB): -2.5 | Bit Error Rate: 0.2605000138282776
For coderate=0.5 (0.5), At SNR(dB): 0.0 | Bit Error Rate: 0.15962499380111694
For coderate=0.5 (0.5), At SNR(dB): 2.5 | Bit Error Rate: 0.0043750000186264515
For coderate=0.5 (0.5), At SNR(dB): 5.0 | Bit Error Rate: 0.0
For coderate=0.75 (0.7547169811320755), At SNR(dB): -5.0 | Bit Error Rate: 0.328000009059906
For coderate=0.75 (0.7547169811320755), At SNR(dB): -2.5 | Bit Error Rate: 0.2867499887943268
For coderate=0.75 (0.7547169811320755), At SNR(dB): 0.0 | Bit Error Rate: 0.2199999988079071
For coderate=0.75 (0.7547169811320755), At SNR(dB): 2.5 | Bit Error Rate: 0.1366250067949295
For coderate=0.75 (0.7547169811320755), At SNR(dB): 5.0 | Bit Error Rate: 0.008500000461935997
For coderate=0.89 (0.8888888888888888), At SNR(dB): -5.0 | Bit Error Rate: 0.3269999921321869
For coderate=0.89 (0.8888888888888888), At SNR(dB): -2.5 | Bit Error Rate: 0.2801249921321869
For coderate=0.89 (0.8888888888888888), At SNR(dB): 0.0 | Bit Error Rate: 0.22887499630451202
For coderate=0.89 (0.8888888888888888), At SNR(dB): 2.5 | Bit Error Rate: 0.16962499916553497
For coderate=0.89 (0.8888888888888888), At SNR(dB): 5.0 | Bit Error Rate: 0.07000000029802322

Performance Evaluation: BER vs SNR for different code-rates

The script plots SNR in dB vs BER performance at code-rate \(= \{0.33, 0.4, 0.5, 0.67, 0.75, 0.83, 0.89\}\). It can be observed that the Reed Muller codes can provide the reliablilty of

  • \(99.99990 \text{ or BER = } 10^{-4}\) for coderate \(\leq 0.5\) at an SNR value \(\leq 5\)dB.

  • For higher coderates higher SNR values are required to achieve the same performance.

Furthermore, the following averaged over \(500000\) batches (monteCarloIterations).

[6]:
fig, ax = plt.subplots()

color_tuple     = ['blue', 'orange', 'green',   'red',    'purple', 'brown',       'pink',  'gray', 'olive', 'cyan', 'black']
markcolor_tuple = ['gold', 'navy',   'crimson', 'yellow', 'line',   'springgreen', 'black', 'aqua', 'royalblue', 'red', 'green']
linestyle_tuple = ['-', '--', '-.', ':', 'solid', 'dashed', 'dashdot', 'dotted']
marker_tuple    = [".", "o", ">", "2", "8", "s", "p", "*", "P", "X", "D"]

rIndex = codeRate.size-1
for coderate in np.flip(codeRate):
    ax.semilogy(SNRdB, BER[rIndex], color=color_tuple[rIndex%len(color_tuple)],
                linestyle=linestyle_tuple[rIndex%len(linestyle_tuple)], lw = 2,
                marker=marker_tuple[rIndex%len(marker_tuple)], markersize = 6, label="code rate = "+str(coderate))

    rIndex = rIndex - 1

ax.legend(loc="lower left")
ax.set_xlabel("Signal to Noise Ratio (dB)", fontsize = 9)
ax.set_ylabel("Bit Error Rate", fontsize = 9)
ax.set_title("[LDPC Codes]: BER vs SNR(dB) for different code-rates", fontsize = 12)
plt.rcParams.update({'font.size': 9})
plt.show()
../../../_images/api_Tutorials_Tutorial4_Tutorial4_LDPCCodes_11_0.png

Simulation: Variation in Reliability with block-length for fixed coderate

[7]:
tbSize     = np.array([32, 128, 512, 2048, 8192])
numBatches = 100

codeRate   = np.array([0.5])
SNRdB      = np.linspace(-5,5,5)
SNR        = 10**(0.1*SNRdB)

BER        = np.zeros((tbSize.size, SNRdB.size))
rIndex     = 0
snrIndex   = 0
tbIndex    = 0

for tbsize in tbSize:
    rIndex     = 0
    for coderate in codeRate:
        lpdcConfig = LDPCparameters(tbsize, coderate)
        k          = lpdcConfig.k_ldpc

        bg         = lpdcConfig.baseGraph
        zc         = lpdcConfig.liftingfactor
        numCBs     = lpdcConfig.numCodeBlocks
        N          = lpdcConfig.n

        bits       = np.float32(np.random.randint(2, size = (numBatches, numCBs, k), dtype=np.int8))
        encoder    = LDPCEncoder5G(k, bg, zc)
        encBits    = encoder(bits)
        E          = int(k/coderate)
        rateMatch  = BitSelection(numLayers=1, modOrder=2, numCodedBits=E, baseGraph=bg, enableLBRM=False, tbSize=tbSize, numCBs=numCBs)
        codeword   = rateMatch(encBits, rvID=0)
        symbols    = mapper(codeword[0])
        snrIndex   = 0
        for snr in SNR:

            symbs      = symbols + np.sqrt(0.5/snr)*(np.random.standard_normal(size=symbols.shape)+1j*np.random.standard_normal(size=symbols.shape)).astype(np.complex64)

            llrs       = demapper([symbs , np.float32(1/snr)])

            rxCodeword = BitDeselection(np.array([]), bg, zc, False)([llrs], rvID=0)
            decoder    = LDPCDecoder5G(bg, zc)
            decBits    = decoder(rxCodeword)
            BER[tbIndex, snrIndex] = np.mean(np.abs(decBits-bits))
            print("For tbsize="+str(tbsize)+" ("+str(k/codeword[0].shape[-1])+"), At SNR(dB): "+str(SNRdB[snrIndex])+" | Bit Error Rate: "+str(BER[tbIndex, snrIndex]))
            snrIndex += 1
        rIndex   += 1
    tbIndex   += 1

For tbsize=32 (0.5), At SNR(dB): -5.0 | Bit Error Rate: 0.31712499260902405
For tbsize=32 (0.5), At SNR(dB): -2.5 | Bit Error Rate: 0.273250013589859
For tbsize=32 (0.5), At SNR(dB): 0.0 | Bit Error Rate: 0.15049999952316284
For tbsize=32 (0.5), At SNR(dB): 2.5 | Bit Error Rate: 0.011749999597668648
For tbsize=32 (0.5), At SNR(dB): 5.0 | Bit Error Rate: 0.0
For tbsize=128 (0.5), At SNR(dB): -5.0 | Bit Error Rate: 0.3294583261013031
For tbsize=128 (0.5), At SNR(dB): -2.5 | Bit Error Rate: 0.26495832204818726
For tbsize=128 (0.5), At SNR(dB): 0.0 | Bit Error Rate: 0.16341666877269745
For tbsize=128 (0.5), At SNR(dB): 2.5 | Bit Error Rate: 0.0
For tbsize=128 (0.5), At SNR(dB): 5.0 | Bit Error Rate: 0.0
For tbsize=512 (0.5), At SNR(dB): -5.0 | Bit Error Rate: 0.3240416646003723
For tbsize=512 (0.5), At SNR(dB): -2.5 | Bit Error Rate: 0.26758334040641785
For tbsize=512 (0.5), At SNR(dB): 0.0 | Bit Error Rate: 0.15947222709655762
For tbsize=512 (0.5), At SNR(dB): 2.5 | Bit Error Rate: 0.0
For tbsize=512 (0.5), At SNR(dB): 5.0 | Bit Error Rate: 0.0
For tbsize=2048 (0.5), At SNR(dB): -5.0 | Bit Error Rate: 0.3271682560443878
For tbsize=2048 (0.5), At SNR(dB): -2.5 | Bit Error Rate: 0.2666490375995636
For tbsize=2048 (0.5), At SNR(dB): 0.0 | Bit Error Rate: 0.16367307305335999
For tbsize=2048 (0.5), At SNR(dB): 2.5 | Bit Error Rate: 0.0
For tbsize=2048 (0.5), At SNR(dB): 5.0 | Bit Error Rate: 0.0
For tbsize=8192 (0.5), At SNR(dB): -5.0 | Bit Error Rate: 0.3027746081352234
For tbsize=8192 (0.5), At SNR(dB): -2.5 | Bit Error Rate: 0.24099431931972504
For tbsize=8192 (0.5), At SNR(dB): 0.0 | Bit Error Rate: 0.14345407485961914
For tbsize=8192 (0.5), At SNR(dB): 2.5 | Bit Error Rate: 0.0
For tbsize=8192 (0.5), At SNR(dB): 5.0 | Bit Error Rate: 0.0

Performance Evaluation: BER vs SNR for different block lengths

[8]:
fig, ax = plt.subplots()

color_tuple     = ['blue', 'orange', 'green',   'red',    'purple', 'brown',       'pink',  'gray', 'olive', 'cyan', 'black']
markcolor_tuple = ['gold', 'navy',   'crimson', 'yellow', 'line',   'springgreen', 'black', 'aqua', 'royalblue', 'red', 'green']
linestyle_tuple = ['-', '--', '-.', ':', 'solid', 'dashed', 'dashdot', 'dotted']
marker_tuple    = [".", "o", ">", "2", "8", "s", "p", "*", "P", "X", "D"]

rIndex = 0
for tbsize in tbSize:
    ax.semilogy(SNRdB, BER[rIndex], color=color_tuple[rIndex%len(color_tuple)],
                linestyle=linestyle_tuple[rIndex%len(linestyle_tuple)], lw = 2,
                marker=marker_tuple[rIndex%len(marker_tuple)], markersize = 6, label="block-length = "+str(tbsize))

    rIndex = rIndex + 1

ax.legend(loc="lower left")
ax.set_xlabel("Signal to Noise Ratio (dB)", fontsize = 9)
ax.set_ylabel("Bit Error Rate", fontsize = 9)
ax.set_title("[LDPC Codes]: BER vs SNR(dB) for different block-length for fixxed code-rate = 0.5", fontsize = 12)
plt.rcParams.update({'font.size': 9})
plt.show()
../../../_images/api_Tutorials_Tutorial4_Tutorial4_LDPCCodes_15_0.png
[ ]: