TDL Channel

This notebook demonstrates how to use a TDL channel in both time and frequency domains.

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

from neoradium import Carrier, Modem, TdlChannel, Grid, random
from neoradium.utils import getNmse
[2]:
random.setSeed(123)                                    # Make results reproducible
carrier = Carrier(startRb=0, numRbs=25, spacing=15)    # Carrier with 25 resource blocks and 15 kHz subcarrier spacing
bwp = carrier.curBwp                                   # The only bandwidth part (BWP) in the carrier
txGrid = bwp.createGrid(numPorts=8)                    # Create an empty resource grid

stats = txGrid.getStats()                              # Get statistics about the resource grid
modem = Modem("16QAM")                                 # Use 16QAM modulation
numRandomBits = stats['UNASSIGNED']*modem.qm           # Total number of bits available in the resource grid

bits = random.bits(numRandomBits)                      # Generate random bits
symbols = modem.modulate(bits)                         # Modulate bits to obtain symbols

Indices = txGrid.getReIndexes("UNASSIGNED")            # Indices of the "UNASSIGNED" resources
txGrid[Indices] = symbols                              # Map symbols to the resource grid

txWaveform = txGrid.ofdmModulate()                     # OFDM-modulate the resource grid to obtain a waveform

print("Shape of Resource Grid:",txGrid.shape)
print("Shape of Waveform:     ",txWaveform.shape)

Shape of Resource Grid: (8, 14, 300)
Shape of Waveform:      (8, 30720)
[3]:
# Create a TDL-D channel model with 4 GHz carrier frequency and 50 Hz Doppler shift
channel = TdlChannel(bwp, 'D', carrierFreq=4e9, dopplerShift=50,
                     txAntennaCount=8, rxAntennaCount=2, mimoCorrelation='Medium')
print(channel)

TDL-D Channel Properties:
  carrierFreq:      4 GHz
  normalizeGains:   True
  normalizeOutput:  True
  txDir:            Downlink
  filterLen:        16 samples
  delayQuantSize:   64
  stopBandAtten:    80 dB
  dopplerShift:     50 Hz
  coherenceTime:    8.463 milliseconds
  delaySpread:      30 ns
  pathDelays (ns):  0.0000 1.0500 18.360 40.890 42.150 54.120 77.880 53.250 121.26 238.11
                    282.72 291.24 375.75
  pathPowers (dB):  -0.001 -18.80 -21.00 -22.80 -17.90 -20.10 -21.90 -22.90 -27.80 -23.60
                    -24.80 -30.00 -27.70
  hasLOS:           True
  kFactorLos (dB):  13.300
  rxAntennaCount:   2
  txAntennaCount:   8
  mimoCorrelation:  Medium
  polarization:     CoPolar
  sosType:          GMEDS1
  sosNumSins:       32

[4]:
# Apply the channel in the frequency domain:
t0 =time.time()
channelMatrix = channel.getChannelMatrix()
rxGridF = txGrid.applyChannel(channelMatrix)
t1 =time.time()
print("Time to apply channel in Freq. Domain:", t1-t0)
Time to apply channel in Freq. Domain: 0.005537986755371094
[5]:
# Apply the channel in the time domain and demodulate to obtain a received resource grid (rxGrid)
t0 =time.time()
maxDelay = channel.getMaxDelay()                         # Calculate the channel maximum delay
paddedTxWaveform = txWaveform.pad(maxDelay)              # Pad the waveform with zeros
rxWaveform = channel.applyToSignal(paddedTxWaveform)     # Apply the channel to the waveform
offset = channel.getTimingOffset()                       # Get timing offset for synchronization
syncedWaveform = rxWaveform.sync(offset)                 # Apply timing synchronization
rxGridT = syncedWaveform.ofdmDemodulate(bwp)             # OFDM-demodulate the waveform to obtain a resource grid
t1 =time.time()
print("Time to apply channel in Time Domain:", t1-t0)
print("NMSE between the rxGrid in Time and Freq. domains: ", getNmse(rxGridT.grid,rxGridF.grid))

Time to apply channel in Time Domain: 0.09933590888977051
NMSE between the rxGrid in Time and Freq. domains:  2.5408124405377087e-17
[ ]: