Customizing CDL Channels

This notebook demonstrates how to customize CDL channel models.

[1]:
import numpy as np
import scipy.io
import time

from neoradium import Carrier, Modem, CdlChannel, AntennaPanel, Grid, random
from neoradium.utils import getNmse, toLinear
[2]:
random.setSeed(123)                                    # Make results reproducible
carrier = Carrier(startRb=0, numRbs=25, spacing=15)    # Carrier with 25 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

Indices = txGrid.getReIndexes("UNASSIGNED")            # Indices of the "UNASSIGNED" resources
txGrid[Indices] = 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, 300)
Shape of Waveform:      (8, 30720)

Angle Scaling

[3]:
# Create a CDL-C channel model with 300 ns delay spread, 4 GHz carrier frequency, and 5 Hz Doppler shift
# with angle scaling
channel = CdlChannel(bwp, 'D', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
                     txAntenna = AntennaPanel([2,4], polarization="|"),       # 8 TX antennas
                     rxAntenna = AntennaPanel([1,2], polarization="|"),       # 2 RX antennas
                     angleScaling = ([25, 45, 100, 85],         # Mean values for AoDs, AoAs, ZoDs, ZoAs
                                     [20, 20, 5, 5]))           # Spread values for AoDs, AoAs, ZoDs, ZoAs
print(channel)

CDL-D 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°
  Angle Scaling:
    Means:                  25° 45° 100° 85°
    RMS Spreads:            20° 20° 5° 5°
  xPolPower:                11.00 dB
  angleSpreads:             5° 8° 3° 3°
  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:                   True
  LOS Path:
    Delay (ns):             0.00000
    Power (dB):             -0.20000
    AOD (Deg):              0.0
    AOA (Deg):              -180.0
    ZOD (Deg):              98.0
    ZOA (Deg):              82.0
  NLOS Paths (13):
    Delays (ns):            0.000 10.50 183.6 408.9 421.5 541.2 778.8 532.5 1212. 2381. 2827. 2912. 3757.
    Powers (dB):            -13.5 -18.8 -21.0 -22.8 -17.9 -20.1 -21.9 -22.9 -27.8 -23.6 -24.8 -30.0 -27.7
    AODs (Deg):             0    89   89   89   13   13   13   35   -64  -33  53   -132 77
    AOAs (Deg):             -180 89   89   89   163  163  163  -137 74   128  -120 -9   -84
    ZODs (Deg):             98   86   86   86   98   98   98   98   88   91   104  80   86
    ZOAs (Deg):             82   87   87   87   79   79   79   78   74   78   87   71   73

Customizing an Existing CDL Profile

[4]:
# Create a CDL-D channel model where the original delays are doubled and
# the original path powers are attenuated by 3 dB
# Note: If you want to set your own delays:
#   - Values must be in normalized form similar to those in TR 38.901,
#     Tables 7.7.1-1 to 7.7.1-5. They are scaled using 'delaySpread'.
#   - The values must be in increasing order.
delays = CdlChannel.getCdlParams('D', "delay") * 2
powers = CdlChannel.getCdlParams('D', "power") - 3
channel = CdlChannel(bwp, 'D', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
                     pathDelays = delays,
                     pathPowers = powers,
                     txAntenna = AntennaPanel([2,4], polarization="|"),       # 8 TX antennas
                     rxAntenna = AntennaPanel([1,2], polarization="|"))       # 2 RX antennas
print(channel)

CDL-D 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:                11.00 dB
  angleSpreads:             5° 8° 3° 3°
  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:                   True
  LOS Path:
    Delay (ns):             0.00000
    Power (dB):             -3.20000
    AOD (Deg):              0.0
    AOA (Deg):              -180.0
    ZOD (Deg):              98.0
    ZOA (Deg):              82.0
  NLOS Paths (13):
    Delays (ns):            0.000 21.00 367.2 817.8 843.0 1082. 1557. 1065. 2425. 4762. 5654. 5824. 7515.
    Powers (dB):            -16.5 -21.8 -24.0 -25.8 -20.9 -23.1 -24.9 -25.9 -30.8 -26.6 -27.8 -33.0 -30.7
    AODs (Deg):             0    89   89   89   13   13   13   35   -64  -33  53   -132 77
    AOAs (Deg):             -180 89   89   89   163  163  163  -137 74   128  -120 -9   -84
    ZODs (Deg):             98   86   86   86   98   98   98   98   88   91   104  80   86
    ZOAs (Deg):             82   87   87   87   79   79   79   78   74   78   87   71   73

Fully Customized CDL Channel Model

[5]:
# Create a CDL channel model with 6 non-line-of-sight (NLOS) paths:
# Notes:
#   - The profile must be set to None to indicate a customized CDL model.
#   - The delays must be in increasing order.
#   - The delays must be in nanoseconds ('delaySpread' is ignored).
#   - Azimuth angles are in the range [-180°, 180°].
#   - Zenith angles are in the range [0°, 180°] (90° is horizontal).
#   - Path powers are specified in dB.
#   - If a line-of-sight (LOS) path exists, the path information should follow the
#     CDL rules for specular and Laplacian paths. Refer to TR 38.901, Tables
#     7.7.1-4 and 7.7.1-5 for CDL-D and CDL-E, respectively, and follow those patterns.
#   - Set the 'hasLos' argument based on your path information.
delays = np.array([4, 50, 120, 140, 170, 200])                          # Delays (ns)
powers = np.array([-1.4, -2, -2.2, -4, -6, -8.2])                       # Path powers in dB
aods = np.array([-160, -7, -3, 131, -81, 148])                          # Azimuth angles of departure (degrees)
aoas = np.array([54, 152, -14, 121, -61, -95])                          # Azimuth angles of arrival (degrees)
zods = np.array([92, 112, 85, 70, 120, 96])                             # Zenith angles of departure (degrees)
zoas = np.array([95, 103, 108, 84, 81, 115])                            # Zenith angles of arrival (degrees)

channel = CdlChannel(bwp, profile=None,                                 # Profile is set to None
                     carrierFreq=4e9, dopplerShift=5,
                     pathDelays = delays, pathPowers = powers,
                     aods = aods, aoas = aoas,
                     zods = zods, zoas = zoas,
                     hasLos = False,
                     angleSpreads = [5, 12, 4, 4],                      # Customized angle spreads
                     txAntenna = AntennaPanel([2,4], polarization="|"), # 8 TX antennas
                     rxAntenna = AntennaPanel([1,2], polarization="|")) # 2 RX antennas

print(channel)

Customized CDL 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
  ueDirAZ:                  0°, 90°
  xPolPower:                10.00 dB
  angleSpreads:             5° 12° 4° 4°
  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 (6):
    Delays (ns):            4.000 50.00 120.0 140.0 170.0 200.0
    Powers (dB):            -1.40 -2.00 -2.20 -4.00 -6.00 -8.20
    AODs (Deg):             -160 -7   -3   131  -81  148
    AOAs (Deg):             54   152  -14  121  -61  -95
    ZODs (Deg):             92   112  85   70   120  96
    ZOAs (Deg):             95   103  108  84   81   115

[6]:
# 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.003696918487548828
[7]:
# Apply the channel in the time domain and demodulate to obtain a 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.04989504814147949
NMSE between the rxGrid in Time and Freq. domains:  2.8586347526266583e-15
[ ]: