Calculating bit error rate of PDSCH communication

This notebook shows how to calculate the bit error rate of PDSCH communication when there is no channel coding.

[1]:
import numpy as np
import scipy.io
import time
import matplotlib.pyplot as plt

from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, Grid
from neoradium.utils import random
[2]:
numFrames = 2                               # Number of time-domain frames
snrDbs = [5,10,15,20,25,30]                 # SNR values (in dB) for which we want to evaluate the model
freqDomain = False                          # Set this to True to apply channel in frequency domain

modulation = "16QAM"                        # Modulation Scheme
carrier = Carrier(numRbs=51, spacing=30)    # Create a carrier with 51 RBs and 30KHz subcarrier spacing
bwp = carrier.curBwp                        # The only bandwidth part in the carrier

# Create a PDSCH object
pdsch = PDSCH(bwp, interleavingBundleSize=0, numLayers=2, nID=carrier.cellId, modulation=modulation)
pdsch.setDMRS(prgSize=0, configType=2, additionalPos=2)     # Specify the DMRS configuration

numSlots = bwp.slotsPerFrame*numFrames                      # Total number of slots
results = {}                                                # Dictionary to save the results

minMse, maxMse = 100, 0
for chanEstMethod in ["Perfect", "LS"]:               # Two different channel estimation methods
    results[chanEstMethod] = {}
    print("\nSimulating end-to-end for \"%s\", with \"%s\" channel estimation, in %s domain."%
          (modulation, chanEstMethod, "frequency" if freqDomain else "time"))
    print("SNR(dB)   Total Bits   Bit Errors   BER(%)   time(Sec.)")
    print("-------   ----------   ----------   ------   ----------")
    for snrDb in snrDbs:
        random.setSeed(123)                          # Making the results reproducible.
        t0 = time.time()
        carrier.slotNo = 0

        # Creating a CdlChannel object:
        channel = CdlChannel('C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
                             txAntenna = AntennaPanel([2,4]),  # 8 TX antenna
                             rxAntenna = AntennaPanel([1,2]),  # 2 RX antenna
                             seed = 123,
                             timing = "nearest")

        bitErrors = 0
        totalBits = 0

        for slotNo in range(numSlots):
            grid = pdsch.getGrid()                       # Create a resource grid already populated with DMRS
            numBits = pdsch.getBitSizes(grid)[0]         # Actual number of bits available in the resource grid
            txBits = random.bits(numBits)                # Create random binary data

            # Now populate the resource grid with coded data. This includes QAM modulation and resource mapping.
            pdsch.populateGrid(grid, txBits)

            # Store the indexes of the PDSCH data in pdschIndexes to be used later.
            pdschIndexes = pdsch.getReIndexes(grid, "PDSCH")

            # Getting the Precoding Matrix, and precoding the resource grid
            channelMatrix = channel.getChannelMatrix(bwp)           # Get the channel matrix
            precoder = pdsch.getPrecodingMatrix(channelMatrix)      # Get the precoder matrix from PDSCH object
            precodedGrid = grid.precode(precoder)                   # Perform the precoding

            if freqDomain:
                rxGrid = precodedGrid.applyChannel(channelMatrix)   # Apply the channel in frequency domain
                rxGrid = rxGrid.addNoise(snrDb=snrDb)               # Add noise
            else:
                txWaveform = precodedGrid.ofdmModulate()            # OFDM Modulation
                maxDelay = channel.getMaxDelay()                    # Get the max. channel delay
                txWaveform = txWaveform.pad(maxDelay)               # Pad with zeros
                rxWaveform = channel.applyToSignal(txWaveform)      # Apply channel in time domain
                noisyRxWaveform = rxWaveform.addNoise(snrDb=snrDb, nFFT=bwp.nFFT)  # Add noise
                offset = channel.getTimingOffset()                  # Get timing info for synchronization
                syncedWaveform = noisyRxWaveform.sync(offset)       # Synchronization
                rxGrid = syncedWaveform.ofdmDemodulate(bwp)         # OFDM demodulation

            if chanEstMethod == "Perfect":                          # Perfect Channel Estimation
                estChannelMatrix = channelMatrix @ precoder[None,...]
            else:                                                   # LS + Interpolation Channel Estimation
                estChannelMatrix, noiseEst = rxGrid.estimateChannelLS(pdsch.dmrs, polarInt=False,
                                                                      kernel='linear')
            act = channelMatrix @ precoder[None,...]
            mse1 = np.square(np.abs(estChannelMatrix - act)).mean()
            fEst = np.stack([estChannelMatrix.real, estChannelMatrix.imag], axis=4)
            fAct = np.stack([act.real, act.imag], axis=4)
            mse2 = np.square(fEst - fAct).mean()
            if minMse>mse2: minMse=mse2
            if maxMse<mse2: maxMse=mse2

            eqGrid, llrScales = rxGrid.equalize(estChannelMatrix)       # Equalization
            rxBits = pdsch.getHardBitsFromGrid(eqGrid, pdschIndexes)[0] # Demodulation
            bitErrors += np.abs(rxBits-txBits).sum()                    # Calculating number of bit errors
            totalBits += numBits
            print("\r  %3d      %8d     %8d    %6.2f    %6.2f"%(snrDb, totalBits, bitErrors,
                                                                bitErrors*100/totalBits, time.time()-t0), end='')

            carrier.goNext()                        # Prepare the carrier object for the next slot
            channel.goNext()                        # Prepare the channel model for the next slot

        dt = time.time()-t0                         # Total time for this SNR
        results[chanEstMethod][snrDb] = {"totalBits":totalBits,
                                         "bitErrors":bitErrors,
                                         "BER":      bitErrors*100/totalBits,
                                         "Time":     dt}
        print("\r  %3d      %8d     %8d    %6.2f    %6.2f"%(snrDb, totalBits, bitErrors,
                                                            bitErrors*100/totalBits, dt))

# Compare the results in a plot:
for i,chanEstMethod in enumerate(['Perfect', 'LS']):
    bers = [results[chanEstMethod][snrDb]["BER"] for snrDb in snrDbs]
    plt.plot(snrDbs, bers, label=chanEstMethod)
plt.legend()
plt.title("Bit Error Rate for different mothods of Channel Estimation.");
plt.grid()
plt.xlabel("SNR")
plt.xticks(snrDbs)
plt.ylabel("BER (%)")
# plt.yscale('log')
plt.show()

Simulating end-to-end for "16QAM", with "Perfect" channel estimation, in time domain.
SNR(dB)   Total Bits   Bit Errors   BER(%)   time(Sec.)
-------   ----------   ----------   ------   ----------
    5       2545920       758389     29.79      5.71
   10       2545920       478555     18.80      5.77
   15       2545920       208018      8.17      5.82
   20       2545920        42283      1.66      5.81
   25       2545920         2789      0.11      5.78
   30       2545920           27      0.00      5.81

Simulating end-to-end for "16QAM", with "LS" channel estimation, in time domain.
SNR(dB)   Total Bits   Bit Errors   BER(%)   time(Sec.)
-------   ----------   ----------   ------   ----------
    5       2545920       884330     34.74      6.27
   10       2545920       588715     23.12      6.13
   15       2545920       300615     11.81      6.10
   20       2545920        91421      3.59      6.01
   25       2545920        11869      0.47      6.44
   30       2545920          708      0.03      6.01
../../../../_images/source_Playground_Notebooks_PDSCH_PDSCH-BER_2_1.png
[ ]: