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