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