Evaluating Communication Throughput with HARQ
This notebook evaluates end-to-end communication performance using Hybrid Automatic Repeat reQuest (HARQ) and measures the resulting system throughput across a range of signal-to-noise ratio (SNR) values.
The evaluation compares multiple channel estimation methods, including the trained neural network and conventional estimation techniques, within a complete communication pipeline. When decoding errors occur, HARQ retransmissions are used to recover the transmitted data. The throughput achieved by each method is then calculated by accounting for both successful transmissions and the retransmissions required to achieve reliable communication.
Unlike the NMSE evaluation, which measures channel estimation accuracy, and the BLER evaluation, which measures decoding reliability, the HARQ evaluation provides a system-level performance metric that reflects the practical efficiency of the receiver. The resulting throughput curves demonstrate how improvements in channel estimation translate into increased data rates under realistic communication conditions.
[1]:
import numpy as np
import time, os, torch
import matplotlib.pyplot as plt
from neoradium import BandwidthPart, PDSCH, CdlChannel, AntennaPanel, random, SnrScheduler
from ChEstNet import ChEstNet
from ChEstUtils import estimateChannelML, getPseudoPilotIndices
[2]:
# Load the trained model
modelPath = 'Models/Pretrained.pth' # Use the pre-trained model
# modelPath = 'Models/Trained.pth' # Use the model trained in the previous step (see MLChEstTrain.ipynb)
device = "cuda:0" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
model = ChEstNet(device) # Instantiate the model on the target device
model.loadParams(modelPath); # Load the trained model parameters
[3]:
# Calculate communication throughput with HARQ
seed = 123
# Note:
# Using maxSlots = 2000 may cause this experiment to take a long time to complete.
# You can use a smaller value to obtain quicker (though less precise) results. You may
# also use a larger increments for the 'SnrScheduler' below for a coarser set of SNR values.
maxSlots = 2000 # Number of slots
snrScheduler = SnrScheduler(-13,0.5,fastStep=1) # Start at -13 dB, use increments of 0.5 dB
bwp = BandwidthPart(numRbs=45, spacing=30) # Create a BandwidthPart object
# Create a two-layer PDSCH object with type-2 DMRS on symbols 2 and 11
pdsch = PDSCH(bwp, numLayers=2, modulation="16QAM")
pdsch.setDMRS(configType=2, additionalPos=1)
# Create the HARQ entity and its internal LDPC codec object
harq = pdsch.getHarq(coderates=490/1024, # Coderate used by the LDPC codec
numIter=2, # Same as the paper (Default is 5)
harqType="CC", # Use chase combining (CC)
numProc=16) # Number of HARQ processes
# Create the channel model
channel = CdlChannel(bwp, 'C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
txAntenna = AntennaPanel([2,4], polarization="x"), # 16 TX antennas
rxAntenna = AntennaPanel([1,2], polarization="x")) # 4 RX antennas
def equalizeAndDecode(rxGrid, chanEst, errVar=None):
eqGrid, llrScales = pdsch.equalize(rxGrid, chanEst, errVar) # Equalization
llrs = pdsch.getLLRs(eqGrid, llrScales) # Demodulation (to LLRs)
# This is important:
# During the refinement loop, decoding must use the HARQ decode buffer for
# retransmissions without modifying the HARQ state. To do this, we save the
# current HARQ decode buffer, call the LDPC decoder with the HarqCW object so
# that it uses the HARQ buffer, and then restore the saved buffer. This allows
# the decoder to use the HARQ combining state while leaving the HARQ object
# unchanged.
harqCw = harq.curProcess.cws[0] # Since we have 2 layers, there is only one codeword
savedBuffer = None if harqCw.decBuffer is None else np.copy(harqCw.decBuffer) # Save HARQ buffer
decodedTxBlock, crcMatch = harqCw.ldpcCodec.decode(llrs[0], harqCw) # LDPC decoding
harqCw.decBuffer = savedBuffer # Restore HARQ buffer
cbErrors = (crcMatch[1:]==False).sum() # Number of code block errors
return [cbErrors, len(crcMatch)-1], [decodedTxBlock, crcMatch]
chEstMethods = ["LS", "Self-Refining", "Perfect"]
results = {}
for chEstMethod in chEstMethods:
print(f'\nEvaluating throughput with {chEstMethod} channel {"knowledge" if chEstMethod=='Perfect' else "estimation"}')
print("SNR(dB) Tx Bits Rx Bits Throughput(%) TX Blocks RX Blocks BLER(%) Avg. Retransmissions time(Sec.)")
print("------- ---------- ---------- ------------- --------- --------- ------- -------------------- ----------")
snrScheduler.reset()
for snrDb in snrScheduler:
random.setSeed(seed)
channel.restart() # Reset the channel and the bandwidth part
harq.reset() # Reset HARQ state and buffers
retransmissionsSaved = 0
t0 = time.monotonic() # Start the timer
for s in range(maxSlots): # The inner loop doing 'numSlot' transmissions
pdsch.initGrid() # Create and initialize PDSCH's internal grid
numBits = pdsch.getBitCapacity()[0] # Number of bits available in the resource grid
tbs = harq.ldpcCodec.txBlockSizes # Transport block sizes (for each codeword)
# Preparing the transport blocks
txBlocks = [] # Transport blocks, one per codeword.
for c in range(harq.numCW):
if harq.needNewData[c]: txBlocks += [ random.bits(tbs[c]) ] # Create random bits for new transmissions
else: txBlocks += [ None ] # Set to None indicating a retransmission
# The 'harq.encode' function returns a coded, rate-matched bitstream, ready for transmission/retransmission
rateMatchedCBs = harq.encode(txBlocks, numBits)
pdsch.setPdschData(rateMatchedCBs) # Map/modulate the data to the resource grid
channelMatrix = channel.getChannelMatrix() # Get perfect channel matrix
precoder = pdsch.getPrecodingMatrix(channelMatrix) # Get precoding matrix
txGrid = bwp.createGrid(channelMatrix.shape[3]) # Create the transmitted resource grid
pdsch.precodeTo(txGrid, precoder) # Precode PDSCH data into the txGrid
rxGrid = txGrid.applyChannel(channelMatrix) # Apply the channel in the frequency domain
noisyRxGrid = rxGrid.addNoise(snrDb=snrDb) # Add noise
errVar = None # Only used for LS channel estination
if chEstMethod == "Perfect":
pcChannelMatrix = channel.getEffChannel(channelMatrix, precoder) # Ground-truth effective channel
elif chEstMethod == "LS":
pcChannelMatrix, errVar = pdsch.estimateChannel(noisyRxGrid) # LS channel estimation using DMRS
elif chEstMethod == "Self-Refining":
# ML channel estimation using DMRS only
dmrsIdx = pdsch.grid.getReIndexes("DMRS")
pcChannelMatrix = estimateChannelML(model, dmrsIdx, pdsch.grid, noisyRxGrid)
blerInfo, decodeInfo = equalizeAndDecode(noisyRxGrid, pcChannelMatrix)
cbErrors, numCB = blerInfo
decodedTxBlock, crcMatch = decodeInfo
# Now try using pseudo-pilots
# Pseudo-pilots are relevant only when cbErrors ∈ {1,...,numCB-1}
relevantCbErrors = np.arange(1,numCB)
orgCbErrors = cbErrors
while cbErrors in relevantCbErrors:
pseudoPilotIndices = getPseudoPilotIndices(pdsch, harq.ldpcCodec, decodedTxBlock, crcMatch)
allPilotIndices = tuple(np.append(pseudoPilotIndices[i],dmrsIdx[i]) for i in [0,1,2])
pcChannelMatrix = estimateChannelML(model, allPilotIndices, pdsch.grid, noisyRxGrid)
blerInfo, decodeInfo = equalizeAndDecode(noisyRxGrid, pcChannelMatrix)
if blerInfo[0] >= cbErrors: break # No improvement or getting worse
cbErrors = blerInfo[0]
decodedTxBlock, crcMatch = decodeInfo
retransmissionsSaved += 1*(cbErrors==0 and orgCbErrors>0)
# Use the channel matrix (with precoding effect) to equalize the received resource grid
eqGrid, llrScales = pdsch.equalize(noisyRxGrid, pcChannelMatrix, errVar)
# Demodulate the equalized resource grid (eqGrid) to get the Log-Likelihood values
llrs = pdsch.getLLRs(eqGrid, llrScales)
# Use HARQ entity to decode the LLRs to transport blocks
decodedTxBlocks, crcMatches = harq.decode(llrs)
# Get the statistics from HARQ entity and print them:
print(f"{snrDb:^7.2f} {harq.totalTxBits:^10d} {harq.totalRxBits:^10d} {harq.throughput:^13.2f} "
f"{harq.totalTxBlocks:^9} {harq.totalRxBlocks:^9d} {harq.bler:^7.2f} {harq.meanRetransmissions:^20.3f} "
f"{time.monotonic()-t0:^-10.3f}", end="\r")
channel.goNext()
harq.goNext()
snrScheduler.setData(harq.bler, harq.throughput)
print("")
results[chEstMethod] = snrScheduler.getSnrsAndData()
Evaluating throughput with LS channel estimation
SNR(dB) Tx Bits Rx Bits Throughput(%) TX Blocks RX Blocks BLER(%) Avg. Retransmissions time(Sec.)
------- ---------- ---------- ------------- --------- --------- ------- -------------------- ----------
-13.00 53264000 3275736 6.15 2000 123 93.85 2.980 532.557
-13.50 53264000 1304968 2.45 2000 49 97.55 3.000 537.419
-14.00 53264000 133160 0.25 2000 5 99.75 3.000 537.658
-14.50 53264000 0 0.00 2000 0 100.00 3.000 538.004
-15.00 53264000 0 0.00 2000 0 100.00 3.000 538.731
-12.50 53264000 5379664 10.10 2000 202 89.90 2.865 536.027
-12.00 53264000 7323800 13.75 2000 275 86.25 2.669 539.747
-11.50 53264000 8468976 15.90 2000 318 84.10 2.526 543.503
-11.00 53264000 11158808 20.95 2000 419 79.05 2.192 551.229
-10.50 53264000 13742112 25.80 2000 516 74.20 1.893 588.755
-10.00 53264000 15792776 29.65 2000 593 70.35 1.732 575.173
-9.50 53264000 18322816 34.40 2000 688 65.60 1.578 574.993
-9.00 53264000 20826224 39.10 2000 782 60.90 1.447 578.255
-8.50 53264000 24474808 45.95 2000 919 54.05 1.141 594.755
-8.00 53264000 29348464 55.10 2000 1102 44.90 0.795 620.993
-7.50 53264000 33742744 63.35 2000 1267 36.65 0.565 647.310
-7.00 53264000 37471224 70.35 2000 1407 29.65 0.414 664.624
-6.50 53264000 40240952 75.55 2000 1511 24.45 0.321 683.405
-6.00 53264000 42344880 79.50 2000 1590 20.50 0.252 699.462
-5.50 53264000 44475440 83.50 2000 1670 16.50 0.195 710.883
-5.00 53264000 46845688 87.95 2000 1759 12.05 0.133 722.943
-4.50 53264000 49881736 93.65 2000 1873 6.35 0.067 740.728
-4.00 53264000 52038928 97.70 2000 1954 2.30 0.023 755.352
-3.50 53264000 52944416 99.40 2000 1988 0.60 0.006 760.037
-3.00 53264000 53264000 100.00 2000 2000 0.00 0.000 761.469
-2.50 53264000 53264000 100.00 2000 2000 0.00 0.000 758.338
Evaluating throughput with Self-Refining channel estimation
SNR(dB) Tx Bits Rx Bits Throughput(%) TX Blocks RX Blocks BLER(%) Avg. Retransmissions time(Sec.)
------- ---------- ---------- ------------- --------- --------- ------- -------------------- ----------
-13.00 53264000 10386480 19.50 2000 390 80.50 2.252 1161.162
-13.50 53264000 8122760 15.25 2000 305 84.75 2.559 1108.626
-14.00 53264000 6578104 12.35 2000 247 87.65 2.736 1071.672
-14.50 53264000 4554072 8.55 2000 171 91.45 2.917 1079.896
-15.00 53264000 2263720 4.25 2000 85 95.75 3.000 1055.577
-15.50 53264000 319584 0.60 2000 12 99.40 3.000 1037.256
-16.00 53264000 0 0.00 2000 0 100.00 3.000 996.445
-16.50 53264000 0 0.00 2000 0 100.00 3.000 999.791
-12.50 53264000 12756728 23.95 2000 479 76.05 1.964 1168.617
-12.00 53264000 15100344 28.35 2000 567 71.65 1.740 1163.537
-11.50 53264000 16991216 31.90 2000 638 68.10 1.596 1170.653
-11.00 53264000 19414728 36.45 2000 729 63.55 1.461 1204.139
-10.50 53264000 24794392 46.55 2000 931 53.45 1.084 1369.975
-10.00 53264000 30839856 57.90 2000 1158 42.10 0.707 1401.985
-9.50 53264000 35047712 65.80 2000 1316 34.20 0.514 1392.512
-9.00 53264000 38536504 72.35 2000 1447 27.65 0.373 1361.042
-8.50 53264000 41359496 77.65 2000 1553 22.35 0.285 1328.201
-8.00 53264000 43516688 81.70 2000 1634 18.30 0.220 1269.400
-7.50 53264000 45194504 84.85 2000 1697 15.15 0.174 1261.194
-7.00 53264000 47964232 90.05 2000 1801 9.95 0.109 1302.307
-6.50 53264000 51133440 96.00 2000 1920 4.00 0.040 1322.477
-6.00 53264000 52544936 98.65 2000 1973 1.35 0.013 1271.644
-5.50 53264000 53210736 99.90 2000 1998 0.10 0.001 1227.850
-5.00 53264000 53210736 99.90 2000 1998 0.10 0.001 1229.006
-4.50 53264000 53237368 99.95 2000 1999 0.05 0.001 1232.342
-4.00 53264000 53264000 100.00 2000 2000 0.00 0.000 1222.478
-3.50 53264000 53264000 100.00 2000 2000 0.00 0.000 1218.688
Evaluating throughput with Perfect channel knowledge
SNR(dB) Tx Bits Rx Bits Throughput(%) TX Blocks RX Blocks BLER(%) Avg. Retransmissions time(Sec.)
------- ---------- ---------- ------------- --------- --------- ------- -------------------- ----------
-13.00 53264000 13156208 24.70 2000 494 75.30 1.965 555.308
-13.50 53264000 10759328 20.20 2000 404 79.80 2.292 544.844
-14.00 53264000 8602136 16.15 2000 323 83.85 2.556 536.411
-14.50 53264000 7030848 13.20 2000 264 86.80 2.683 537.246
-15.00 53264000 4847024 9.10 2000 182 90.90 2.913 529.748
-15.50 53264000 3249104 6.10 2000 122 93.90 3.000 536.239
-16.00 53264000 905488 1.70 2000 34 98.30 3.000 536.731
-16.50 53264000 26632 0.05 2000 1 99.95 3.000 538.074
-17.00 53264000 0 0.00 2000 0 100.00 3.000 537.050
-17.50 53264000 0 0.00 2000 0 100.00 3.000 538.932
-12.50 53264000 15499824 29.10 2000 582 70.90 1.756 560.218
-12.00 53264000 17630384 33.10 2000 662 66.90 1.623 563.326
-11.50 53264000 20027264 37.60 2000 752 62.40 1.486 571.696
-11.00 53264000 22663832 42.55 2000 851 57.45 1.301 578.721
-10.50 53264000 27351064 51.35 2000 1027 48.65 0.932 600.971
-10.00 53264000 32570936 61.15 2000 1223 38.85 0.620 624.290
-9.50 53264000 36352680 68.25 2000 1365 31.75 0.457 642.241
-9.00 53264000 39548520 74.25 2000 1485 25.75 0.341 659.971
-8.50 53264000 41758976 78.40 2000 1568 21.60 0.270 671.930
-8.00 53264000 43703112 82.05 2000 1641 17.95 0.214 681.947
-7.50 53264000 45940200 86.25 2000 1725 13.75 0.157 694.006
-7.00 53264000 48630032 91.30 2000 1826 8.70 0.093 709.748
-6.50 53264000 51319864 96.35 2000 1927 3.65 0.037 723.073
-6.00 53264000 52651464 98.85 2000 1977 1.15 0.012 727.479
-5.50 53264000 53130840 99.75 2000 1995 0.25 0.003 731.925
-5.00 53264000 53210736 99.90 2000 1998 0.10 0.001 731.507
-4.50 53264000 53264000 100.00 2000 2000 0.00 0.000 732.959
-4.00 53264000 53264000 100.00 2000 2000 0.00 0.000 734.751
[4]:
for i,chEstMethod in enumerate(results.keys()):
plt.plot(results[chEstMethod][0], results[chEstMethod][2], label=chEstMethod)
plt.legend()
plt.title("Throughput for Different Channel Estimation Methods")
plt.grid()
plt.xlabel("SNR (dB)")
plt.ylabel("Throughput (%)")
plt.show()
[ ]:
[ ]:
[ ]: