Low-Density Parity Check (LDPC)

This notebook demonstrates how to use the new LdpcCodec API for LDPC encoding and decoding.

[1]:
import numpy as np
from neoradium import BandwidthPart, PDSCH, LdpcCodec, Grid, CdlChannel, random

Case 1: One Codeword (Number of Layers ≤ 4)

[2]:
# Create an LDPC codec object
txBlockSize = 10000
coderate = 449/1024
modulation = "16QAM"
ldpc = LdpcCodec(modulation, coderate, txBlockSize, numLayers=1)
ldpc.print()

LDPC Encode/Decode Properties:
  Num layers:         1
  Num codewords:      1
  numIter:            5
  nRef:               0
  Modulation:         16QAM
  Coderate:           449/1024
  TBS:                10000
  numLayers:          1
  Base Graph:         1
  Code Block Size:    5280
  Num Code Blocks:    2
  Lifting Size:       240

[3]:
print("Base Graph Shape:", ldpc.cwCodecs[0].baseGraph.shape)
print("8x8 sub-matrix at the \"Double Diagonal\" section:")
for r in ldpc.cwCodecs[0].baseGraph[0:8,22:30]: print("    " + "   ".join("%3d"%x for x in r))
Base Graph Shape: (46, 68)
8x8 sub-matrix at the "Double Diagonal" section:
      1     0    -1    -1    -1    -1    -1    -1
      0     0     0    -1    -1    -1    -1    -1
     -1    -1     0     0    -1    -1    -1    -1
      1    -1    -1     0    -1    -1    -1    -1
     -1    -1    -1    -1     0    -1    -1    -1
    180    -1    -1    -1    -1     0    -1    -1
     -1    -1    -1    -1    -1    -1     0    -1
     -1    -1    -1    -1    -1    -1    -1     0
[4]:
txBlock = random.bits(txBlockSize)                  # Create a random Transport Block
rateMatchedCodeBlocks = ldpc.encode(txBlock)
print("Rate-Matched coded blocks Shape:", rateMatchedCodeBlocks.shape)

Rate-Matched coded blocks Shape: (22808,)
[5]:
# Simple bipolar channel (no noise):
channelOutput = 1 - 2.0*rateMatchedCodeBlocks
[6]:
# LDPC decoding
rxBlock, crcMatch = ldpc.decode(channelOutput)

# Check CRC and compare with the original txBlock
print("crcMatch:", crcMatch)
assert np.abs(rxBlock-txBlock).sum()==0

crcMatch: [ True  True  True]

Case 2: Two Codewords with Different Modulation, Coderate, and TBS Settings

[7]:
txBlockSizes = [10000, 5000]
coderates = [449/1024, 193/1024]
modulations = ["16QAM", "QPSK"]
ldpc = LdpcCodec(modulations, coderates, txBlockSizes, numLayers=6)
ldpc.print()

LDPC Encode/Decode Properties:
  Num layers:           6
  Num codewords:        2
  numIter:              5
  nRef:                 0
  First codeword:
    Modulation:         16QAM
    Coderate:           449/1024
    TBS:                10000
    numLayers:          3
    Base Graph:         1
    Code Block Size:    5280
    Num Code Blocks:    2
    Lifting Size:       240
  Second codeword:
    Modulation:         QPSK
    Coderate:           193/1024
    TBS:                5000
    numLayers:          3
    Base Graph:         2
    Code Block Size:    2560
    Num Code Blocks:    2
    Lifting Size:       256

[8]:
txBlocks = [random.bits(txBlockSize) for txBlockSize in txBlockSizes]   # Create random Transport Blocks
rateMatchedCodeBlocks = ldpc.encode(txBlocks)
print("Rate-Matched coded block lengths:")
print(f"  First codeword:  {len(rateMatchedCodeBlocks[0])}")
print(f"  Second codeword: {len(rateMatchedCodeBlocks[1])}")
Rate-Matched coded block lengths:
  First codeword:  22812
  Second codeword: 26532
[9]:
# Simple bipolar channel with no noise:
channelOutput = [1 - 2.0*rateMatchedCodeBlocks[i] for i in range(2)]
[10]:
# LDPC decoding
rxBlocks, crcMatches = ldpc.decode(channelOutput)

# Check CRCs and compare with the original txBlocks
print(f"First codeword crcMatch:  {crcMatches[0]}")
print(f"Second codeword crcMatch: {crcMatches[1]}")
assert np.abs(rxBlocks[0]-txBlocks[0]).sum()==0
assert np.abs(rxBlocks[1]-txBlocks[1]).sum()==0

First codeword crcMatch:  [ True  True  True]
Second codeword crcMatch: [ True  True  True]

Case 3: PDSCH End-to-End

[11]:
bwp = BandwidthPart(numRbs=24, spacing=15)          # Create a bandwidth part with 24 RBs and 15 kHz subcarrier spacing
pdsch = PDSCH(bwp, numLayers=1, modulation="16QAM") # Create a 1-layer PDSCH
pdsch.setDMRS()                                     # Use default DMRS configuration
channel = CdlChannel(bwp, profile='C', delaySpread=100, carrierFreq=4e9, dopplerShift=10)   # Create a SISO channel
[12]:
# Get the LDPC codec from PDSCH:
coderate = 449/1024
ldpc = pdsch.getLdpcCodec(coderate)
ldpc.print()

LDPC Encode/Decode Properties:
  Num layers:         1
  Num codewords:      1
  numIter:            5
  nRef:               0
  Modulation:         16QAM
  Coderate:           449/1024
  TBS:                6528
  numLayers:          1
  Base Graph:         1
  Code Block Size:    7040
  Num Code Blocks:    1
  Lifting Size:       320

[13]:
pdsch.initGrid()                                # Initialize PDSCH's internal resource grid and populate it with DMRS
numBits = pdsch.getBitCapacity()                                    # Number of PDSCH data bits in the resource grid
txBlock = random.bits(ldpc.cwCodecs[0].txBlockSize)                 # Random transport block
rateMatchedCodeBlocks = ldpc.encode(txBlock, numBits[0])            # LDPC-encoded and rate-matched bitstream
pdsch.setPdschData(rateMatchedCodeBlocks)                           # Populates the PDSCH's grid with encoded bits
[14]:
channelMatrix = channel.getChannelMatrix()                          # Get channel matrix
precoder = pdsch.getPrecodingMatrix(channelMatrix)                  # Get precoding matrix
effChannelMatrix = channel.getEffChannel(channelMatrix, precoder)   # The effective channel matrix

txGrid = bwp.createGrid(len(channel.txAntenna))                     # Create a Grid for transmission
pdsch.precodeTo(txGrid, precoder)                                   # Precode PDSCH into the txGrid
txGrid.shape
[14]:
(1, 14, 288)
[15]:
snrDb = 10                                                          # Signal to noise ratio in dB
rxGrid = txGrid.applyChannel(channelMatrix)                         # Apply the channel to the precoded resource grid
noisyRxGrid = rxGrid.addNoise(snrDb=snrDb)                          # Add noise to get a noisy received resource grid
noisyRxGrid.shape
[15]:
(1, 14, 288)
[16]:
eqGrid, llrScales = pdsch.equalize(noisyRxGrid, effChannelMatrix)   # Equalize the received noisy resource grid
llrs = pdsch.getLLRs(eqGrid, llrScales)                             # Demodulate to get the log-likelihood ratios
[17]:
rxBlock, crcMatch = ldpc.decode(llrs)                               # Use our LdpcCodec object to decode LLRs

# Check CRC and compare with the original txBlock
print("crcMatch:", crcMatch[0])
assert np.abs(rxBlock[0]-txBlock).sum()==0
crcMatch: [ True]
[ ]: