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