Channel Matrix

This notebook demonstrates different ways of applying a channel to a signal in the 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]:
random.setSeed(123)                                    # Make results reproducible
carrier = Carrier(startRb=0, numRbs=24, spacing=15)    # Carrier with 24 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

indexes = txGrid.getReIndexes("UNASSIGNED")            # Indices of the "UNASSIGNED" resources
txGrid[indexes] = 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, 288)
Shape of Waveform:      (8, 30720)
[3]:
# Create a CDL-C channel model with 300 ns delay spread, 4 GHz carrier frequency, and 5 Hz Doppler shift
channel = CdlChannel(bwp, 'C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
                     txAntenna = AntennaPanel([2,4], polarization="|"),       # 8 TX antennas
                     rxAntenna = AntennaPanel([1,2], polarization="|"))       # 2 RX antennas
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°, 90°
  xPolPower:                7.00 dB
  angleSpreads:             2° 15° 3° 7°
  TX Antenna:
    Total Elements:         8
    spacing:                0.5𝜆, 0.5𝜆
    shape:                  2 rows x 4 columns
    polarization:           |
  RX Antenna:
    Total Elements:         2
    spacing:                0.5𝜆, 0.5𝜆
    shape:                  1 rows x 2 columns
    polarization:           |
    Orientation (𝛼,𝛃,𝛄):     180° 0° 0°
  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  120  -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 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.004002094268798828
[5]:
# Apply the channel in the time domain and demodulate to obtain the 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.31427597999572754
NMSE between the rxGrid in Time and Freq. domains:  6.832524315416123e-09
[ ]:

[ ]: