Channel Matrix
This notebook shows different ways of calculating the channel matrix and compares the application of channels matrixes in time domain vs frequency domain.
[1]:
import numpy as np
import scipy.io
import time
from neoradium import Carrier, Modem, CdlChannel, AntennaPanel, Grid, random
[2]:
# Create a valid random grid
random.setSeed(123) # Make results reproducible
carrier = Carrier(startRb=0, numRbs=25, spacing=15) # Crrier 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, 9216)
[3]:
# Create a CDL-C channel model with 300ns delay spread, 4GHz carrier frequency, and 5Hz doppler shift
channel = CdlChannel('C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
txAntenna = AntennaPanel([2,4], polarization="|"), # 8 TX antenna
rxAntenna = AntennaPanel([1,2], polarization="|"), # 2 RX antenna
timing="Nearest") # Nearest Neighbors interpolation (Also try: "Polar", "linear")
print(channel)
CDL-C Channel Properties:
delaySpread: 300 ns
dopplerShift: 5 Hz
carrierFreq: 4000000000.0 Hz
normalizeGains: True
normalizeOutput: True
txDir: Downlink
timing method: Nearest
coherenceTime: 0.084628 (Sec.)
ueDirAZ: 0°, 90°
pathDelays (ns): 0.0000 62.970 66.570 69.870 65.280 190.98 193.44 196.80 197.52 238.05
246.39 280.08 368.55 392.49 651.12 813.15 1277.6 1380.0 1647.0 1682.3
1891.9 1991.2 2112.8 2595.6
pathPowers (db): -4.400 -1.200 -3.500 -5.200 -2.500 0.0000 -2.200 -3.900 -7.400 -7.100
-10.70 -11.10 -5.100 -6.800 -8.700 -13.20 -13.90 -13.90 -15.80 -17.10
-16.00 -15.70 -21.60 -22.80
AODs (Degree): -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 (Degree): -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 (Degree): 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 (Degree): 88 72 72 72 70 75 75 75 67 64 71 60 91 60 61
101 62 67 53 62 52 62 58 57
hasLOS: False
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: |
taper: 1.0
RX Antenna:
Total Elements: 2
spacing: 0.5𝜆, 0.5𝜆
shape: 1 rows x 2 columns
polarization: |
taper: 1.0
Channel Filter:
filterDelay (samples): 7
numTxAntenna: 8
numPaths: 24
pathDelays (ns): 0.0000 62.970 66.570 69.870 65.280 190.98 193.44 196.80 197.52 238.05
246.39 280.08 368.55 392.49 651.12 813.15 1277.6 1380.0 1647.0 1682.3
1891.9 1991.2 2112.8 2595.6
filterLen: 16
numInterpol: 50
normalize: True
stopBandAtten: 80
[4]:
t0 =time.time()
# This is the experimental method (Only used for verification and debugging). It calculates the
# channel matrix by first creating a fake grid, OFDM-modulating it, and applying the channel to
# the waveform. It then OFDM-demodulate the waveform to get the channel matrix. This is slowest but
# closest to the effect of applying the channel in time domain.
channelMatrixTD0 = channel.getChannelMatrixTDExp(bwp,1)
t1 =time.time()
print("Time Domain, Experimental:")
print(" Time:", t1-t0)
print(" Channel:\n", channelMatrixTD0[:,0,0,0])
# Getting the channel matrix in time domain and using interpolation to get the channel gains
# at the OFDM symbol times.
channelMatrixTD1 = channel.getChannelMatrix(bwp, 1, timeDomain=True, interpolateTime=True)
t2 = time.time()
print("Time Domain, Time Interpolation:")
print(" Time:", t2-t1)
print(" Channel:\n", channelMatrixTD1[:,0,0,0])
# Getting the channel matrix in time domain without interpolation. The channel gains are
# calculated at the OFDM-symbol times directly. (This is the default config)
channelMatrixTD2 = channel.getChannelMatrix(bwp, 1, timeDomain=True, interpolateTime=False)
t3 = time.time()
print("Time Domain, No Interpolation:")
print(" Time:", t3-t2)
print(" Channel:\n", channelMatrixTD2[:,0,0,0])
# Getting the channel matrix in frequency domain.
channelMatrixFD = channel.getChannelMatrix(bwp, 1, timeDomain=False)
t4 = time.time()
print("Frequency Domain:")
print(" Time:", t4-t3)
print(" Channel:\n", channelMatrixFD[:,0,0,0])
Time Domain, Experimental:
Time: 0.07905101776123047
Channel:
[0.00852756-0.12739031j 0.00853205-0.12739179j 0.00853205-0.12739179j
0.00867352-0.1274171j 0.00908709-0.12725874j 0.00908709-0.12725874j
0.00908488-0.12725828j 0.0090826 -0.12725726j 0.00908709-0.12725874j
0.00908709-0.12725874j 0.00920746-0.12729786j 0.00964038-0.12712568j
0.00964038-0.12712568j 0.00963817-0.12712521j]
Time Domain, Time Interpolation:
Time: 0.004191875457763672
Channel:
[0.00852756-0.12739031j 0.00852756-0.12739031j 0.00852756-0.12739031j
0.00852756-0.12739031j 0.00907741-0.12725851j 0.00907741-0.12725851j
0.00907741-0.12725851j 0.00907741-0.12725851j 0.00907741-0.12725851j
0.00907741-0.12725851j 0.00907741-0.12725851j 0.00962556-0.12712669j
0.00962556-0.12712669j 0.00962556-0.12712669j]
Time Domain, No Interpolation:
Time: 0.0034291744232177734
Channel:
[0.00854752-0.12738554j 0.00862589-0.12736677j 0.00870423-0.12734801j
0.00878253-0.12732925j 0.0088608 -0.12731048j 0.00893904-0.12729172j
0.00901724-0.12727295j 0.00909731-0.12725373j 0.00917544-0.12723497j
0.00925354-0.1272162j 0.0093316 -0.12719743j 0.00940962-0.12717867j
0.00948761-0.1271599j 0.00956557-0.12714113j]
Frequency Domain:
Time: 0.005882978439331055
Channel:
[-0.18121375-0.0519848j -0.18111617-0.05204777j -0.18101976-0.05210998j
-0.18092332-0.05217218j -0.18082688-0.05223438j -0.18073041-0.05229657j
-0.18063394-0.05235875j -0.18053627-0.05242169j -0.18043859-0.05248462j
-0.18034207-0.05254679j -0.18024553-0.05260894j -0.18014899-0.0526711j
-0.18005242-0.05273324j -0.17995584-0.05279538j]
[5]:
# Applying the channel in time domain and demodulate to get a received resource grid (rxGrid)
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
rxGrid = Grid.ofdmDemodulate(bwp, syncedWaveform) # OFDM-demodulation
# Apply the "Experimental" channel matrix:
rxGridTD0 = txGrid.applyChannel(channelMatrixTD0)
print("MSE between the rxGrid & \"Experimental\": ",np.square(np.abs(rxGridTD0.grid-rxGrid.grid)).mean())
# Apply the "Time-domain with interpolation" channel matrix:
rxGridTD1 = txGrid.applyChannel(channelMatrixTD1)
print("MSE between the rxGrid & \"Time domain (Interpolation)\": ",np.square(np.abs(rxGridTD1.grid-rxGrid.grid)).mean())
# Apply the "Time-domain without interpolation" channel matrix:
rxGridTD2 = txGrid.applyChannel(channelMatrixTD2)
print("MSE between the rxGrid & \"Time domain (No Interpolation)\":",np.square(np.abs(rxGridTD2.grid-rxGrid.grid)).mean())
# Apply the "Frequency-domain" channel matrix:
rxGridFD = txGrid.applyChannel(channelMatrixFD)
print("MSE between the rxGrid & \"Frequency domain\": ",np.square(np.abs(rxGridFD.grid-rxGrid.grid)).mean())
MSE between the rxGrid & "Experimental": 2.3647531619647926e-07
MSE between the rxGrid & "Time domain (Interpolation)": 2.399270605372296e-07
MSE between the rxGrid & "Time domain (No Interpolation)": 2.893382278673798e-07
MSE between the rxGrid & "Frequency domain": 0.39108756885752305
[ ]: