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
[ ]: