Channel Matrix

This notebook shows different ways of applying a channel to a signal in time and frequency domains.

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

from neoradium import Carrier, Modem, CdlChannel, AntennaPanel, Grid, random
from neoradium.utils import getNmse
[2]:
# Create a valid random grid
random.setSeed(123)                                    # Make results reproducible
carrier = Carrier(startRb=0, numRbs=25, spacing=15)    # Carrier 25 Resource Blocks, 15KHz subcarrier spacing
bwp = carrier.curBwp                                   # The only bandwidth part in the carrier
txGrid = bwp.createGrid(numPlanes=8)                   # Create an empty resource grid

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

bits = random.bits(numRandomBits)                      # Create random bits
symbols = modem.modulate(bits)                         # Modulate the bits to get symbols

indexes = txGrid.getReIndexes("UNASSIGNED")            # Indexes of the "UNASSIGNED" resources
txGrid[indexes] = symbols                              # Put symbols in the resource grid

txWaveform = txGrid.ofdmModulate()                     # OFDM-Modulate the resource grid to get 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 CDL-C channel model with 300ns delay spread, 4GHz carrier frequency, and 5Hz doppler shift
channel = CdlChannel(bwp, 'C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
                     txAntenna = AntennaPanel([2,4], polarization="|"),       # 8 TX antenna
                     rxAntenna = AntennaPanel([1,2], polarization="|"))       # 2 RX antenna
print(channel)

CDL-C Channel Properties:
  carrierFreq:          4 GHz
  normalizeGains:       True
  normalizeOutput:      True
  txDir:                Downlink
  filterLen:            16 samples
  delayQuantSize:       64
  stopBandAtten:        80 db
  dopplerShift:         5 Hz
  coherenceTime:        84.628 milliseconds
  delaySpread:          300 ns
  ueDirAZ:              0.0°, 90.0°
  Cross Pol. Power:     7 db
  angleSpreads:         2° 15° 3° 7°
  TX Antenna:
    Total Elements:     8
    spacing:            0.5𝜆, 0.5𝜆
    shape:              2 rows x 4 columns
    polarization:       |
    Orientation (𝛼,𝛃,𝛄): 0° 0° 0°
  RX Antenna:
    Total Elements:     2
    spacing:            0.5𝜆, 0.5𝜆
    shape:              1 rows x 2 columns
    polarization:       |
  hasLOS:               False
  NLOS Paths (24):
    Delays (ns):        0.000 62.97 66.57 69.87 65.28 190.9 193.4 196.8 197.5 238.0 246.3 280.0
                        368.5 392.4 651.1 813.1 1277. 1380. 1647. 1682. 1891. 1991. 2112. 2595.
    Powers (db):        -4.40 -1.20 -3.50 -5.20 -2.50 0.000 -2.20 -3.90 -7.40 -7.10 -10.7 -11.1
                        -5.10 -6.80 -8.70 -13.2 -13.9 -13.9 -15.8 -17.1 -16.0 -15.7 -21.6 -22.8
    AODs (Deg):         -47  -23  -23  -23  -41  0    0    0    73   -64  80   -97
                        -55  -64  -78  103  99   89   -102 92   93   107  119  -124
    AOAs (Deg):         -101 120  120  120  -128 170  170  170  55   66   -48  47
                        68   -69  82   31   -16  4    -14  10   6    1    -22  34
    ZODs (Deg):         97   99   99   99   101  99   99   99   105  95   106  94
                        104  104  93   104  95   93   92   107  93   93   105  108
    ZOAs (Deg):         88   72   72   72   70   75   75   75   67   64   71   60
                        91   60   61   101  62   67   53   62   52   62   58   57

[4]:
# Apply the channel in 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.004931926727294922
[5]:
# Applying the channel in time domain and demodulate to get a received resource grid (rxGrid)
t0 =time.time()
maxDelay = channel.getMaxDelay()                         # Calculate the channel's max delay
paddedTxWaveform = txWaveform.pad(maxDelay)              # Pad the waveform with zeros
rxWaveform = channel.applyToSignal(paddedTxWaveform)     # Apply the channel to the waveform
offset = channel.getTimingOffset()                       # Get the timing offset for synchronization
syncedWaveform = rxWaveform.sync(offset)                 # Synchronization
rxGridT = Grid.ofdmDemodulate(bwp, syncedWaveform)       # OFDM-demodulation
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.28674793243408203
NMSE between the rxGrid in Time and Freq. domains:  6.7222404693767e-10
[ ]: