Polar Coding

This notebook demonstrates how to use the PolarEncoder and PolarDecoder classes for Polar encoding and decoding.

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

from neoradium import PolarEncoder, PolarDecoder, random, Modem
[2]:
payloadLen = 30           # A (Must be no larger than 1706)
rateMatchedLen = 120

ebNo = 0.8
coderate = (payloadLen+24)/rateMatchedLen  # Effective code rate

bps = 2;                             # bits per symbol = 2 for QPSK
esNo = ebNo + 10*np.log10(bps)       # energy per symbol over noise
snrdB = esNo + 10*np.log10(coderate) # SNR in dB
noiseVar = 1/(10**(snrdB/10))
noiseStd = np.sqrt(noiseVar)
print(f"Coderate:           {coderate}")
print(f"SNR:                {snrdB:.2f} dB")
print(f"Noise Variance:     {noiseVar:.2f}")
print(f"Noise STD:          {noiseStd:.2f}")

Coderate:           0.45
SNR:                0.34 dB
Noise Variance:     0.92
Noise STD:          0.96
[3]:
# Create a Polar encoder
polarEncoder = PolarEncoder(payloadLen, rateMatchedLen, 'dci')
polarEncoder.print()


Polar Encoder Properties:
  dataType:                        DCI
  payloadSize (A):                 30
  rateMatchedLen (E):              120
  codeBlockSize (K):               54
  polarCodeSize (N):               128
  Max Log2(N) (nMax):              9
  Segmentation (iSeg):             Disabled
  Code Block CRC (crcPoly):        24C
  Input Interleaving (iIL):        Enabled
  Coded bit Interleaving (iBIL):   Disabled
  Parity-check bits (nPC, nPCwm):  0,0

[4]:
random.setSeed(123)             # Make results reproducible
txpBlock = random.bits(30)      # Create random bit stream
print("Transport Block Shape: ", txpBlock.shape)
print("Transport Block:       ", "".join(str(x) for x in txpBlock))

# Perform segmentation
codeBlocks = polarEncoder.doSegmentation(txpBlock)
print("Code block Shape:      ", codeBlocks.shape)
print("Code block:            ", "".join(str(x) for x in codeBlocks[0]))
Transport Block Shape:  (30,)
Transport Block:        111011110011101011110100010011
Code block Shape:       (1, 54)
Code block:             111011110011101011110100010011010100001100000101110011
[5]:
# Perform Polar encoding
codedBlocks = polarEncoder.encode(codeBlocks)
print("Coded block Shape:        ", codedBlocks.shape)
print("Coded block first 10 bits:", "".join(str(x) for x in codedBlocks[0][:10]))

Coded block Shape:         (1, 128)
Coded block first 10 bits: 1110101110
[6]:
# Perform rate matching
rateMatchedCodedBlocks = polarEncoder.rateMatch(codedBlocks)
print("Rate-Matched Shape:  ", rateMatchedCodedBlocks.shape)
print("First 10 bits:       ", "".join(str(x) for x in rateMatchedCodedBlocks[0][:10]))

Rate-Matched Shape:   (1, 120)
First 10 bits:        1110101110
[7]:
# QPSK modulation
modulated = Modem('QPSK').modulate(rateMatchedCodedBlocks)
print("modulated Shape:     ", modulated.shape)

modulated Shape:      (1, 60)
[8]:
noise = random.awgn(modulated.shape, noiseStd)

# Add noise to the modulated signal
rxSymbols = modulated + noise
print("First 5 Rx Symbols:\n", rxSymbols[0,:5])
First 5 Rx Symbols:
 [-0.0815589 -0.31480735j -1.13975742+1.07551113j -0.92231974+0.48795545j
 -0.64105509-1.74439268j  0.10329524+0.25091831j]
[9]:
# Demodulation: calculate LLR values from symbols
llrs = Modem('QPSK').getLLRs(rxSymbols, noiseVar)
print("LLR Shape:     ", llrs.shape)
print("First 10 LLRs: \n", llrs[0,:10])

LLR Shape:      (1, 120)
First 10 LLRs:
 [-0.2496082  -0.96345708 -3.48818838  3.29156482 -2.82272784  1.49337086
 -1.96192704 -5.3386538   0.31613154  0.76792686]
[10]:
# Hard decision
hardCodeBlocks = 1*(llrs<0)
print("Hard Decision first 10 bits:       ", "".join(str(x) for x in hardCodeBlocks[0][:10]))
print("Original Code block first 10 bits: ", "".join(str(x) for x in codeBlocks[0][:10]))

Hard Decision first 10 bits:        1110101100
Original Code block first 10 bits:  1110111100
[11]:
# Create a Polar decoder
polarDecoder = PolarDecoder(payloadLen, rateMatchedLen, 'dci', sclListSize=8, useMinsum=True)
polarDecoder.print()

Polar Decoder Properties:
  dataType:                        DCI
  payloadSize (A):                 30
  rateMatchedLen (E):              120
  codeBlockSize (K):               54
  polarCodeSize (N):               128
  Max Log2(N) (nMax):              9
  Segmentation (iSeg):             Disabled
  Code Block CRC (crcPoly):        24C
  Input Interleaving (iIL):        Enabled
  Coded bit Interleaving (iBIL):   Disabled
  Parity-check bits (nPC, nPCwm):  0,0
  SCL List Size:                   8
  Min-sum Approximation:           Enabled

[12]:
# Perform rate recovery
rateRecoveredRxBlocks = polarDecoder.recoverRate(llrs)
print("Rate Recovered Shape:     ", rateRecoveredRxBlocks.shape)
print("First 10 LLRs: \n", rateRecoveredRxBlocks[0,:10])

Rate Recovered Shape:      (1, 128)
First 10 LLRs:
 [-0.2496082  -0.96345708 -3.48818838  3.29156482 -2.82272784  1.49337086
 -1.96192704 -5.3386538   0.31613154  0.76792686]
[13]:
# Decode the rate-recovered transport blocks
decTxBlock, numCrcErrors = polarDecoder.decode(rateRecoveredRxBlocks)
print("Number of CRC Errors:          ", numCrcErrors)
print("Decoded Transport Block Shape: ", decTxBlock.shape)
print("Decoded Transport Block:       ", "".join(str(x) for x in decTxBlock))
print("Original Transport Block:      ", "".join(str(x) for x in txpBlock))

Number of CRC Errors:           0
Decoded Transport Block Shape:  (30,)
Decoded Transport Block:        111011110011101011110100010011
Original Transport Block:       111011110011101011110100010011
[ ]:

[ ]: