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]
[ ]: