{ "cells": [ { "cell_type": "markdown", "id": "b7e0a807", "metadata": {}, "source": [ "# Customizing CDL Channels\n", "\n", "This notebook demonstrates how to customize CDL channel models." ] }, { "cell_type": "code", "execution_count": 1, "id": "b5013617", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import scipy.io\n", "import time\n", "\n", "from neoradium import Carrier, Modem, CdlChannel, AntennaPanel, Grid, random\n", "from neoradium.utils import getNmse, toLinear" ] }, { "cell_type": "code", "execution_count": 2, "id": "d0952472", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape of Resource Grid: (8, 14, 300)\n", "Shape of Waveform: (8, 30720)\n" ] } ], "source": [ "random.setSeed(123) # Make results reproducible\n", "carrier = Carrier(startRb=0, numRbs=25, spacing=15) # Carrier with 25 resource blocks and 15 kHz subcarrier spacing\n", "bwp = carrier.curBwp # The only bandwidth part (BWP) in the carrier\n", "txGrid = bwp.createGrid(numPorts=8) # Create an empty resource grid\n", "\n", "stats = txGrid.getStats() # Get statistics about the resource grid\n", "modem = Modem(\"16QAM\") # Use 16QAM modulation\n", "numRandomBits = stats['UNASSIGNED']*modem.qm # Total number of bits available in the resource grid\n", "\n", "bits = random.bits(numRandomBits) # Generate random bits\n", "symbols = modem.modulate(bits) # Modulate bits to obtain symbols\n", "\n", "Indices = txGrid.getReIndexes(\"UNASSIGNED\") # Indices of the \"UNASSIGNED\" resources\n", "txGrid[Indices] = symbols # Map symbols to the resource grid\n", "\n", "txWaveform = txGrid.ofdmModulate() # OFDM-modulate the resource grid to obtain a waveform\n", "\n", "print(\"Shape of Resource Grid:\",txGrid.shape)\n", "print(\"Shape of Waveform: \",txWaveform.shape)\n" ] }, { "cell_type": "markdown", "id": "53b08f77-fbc4-476d-b8b2-fcf3504de6b7", "metadata": {}, "source": [ "## Angle Scaling" ] }, { "cell_type": "code", "execution_count": 3, "id": "555f4b1b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "CDL-D Channel Properties:\n", " carrierFreq: 4 GHz\n", " normalizeGains: True\n", " normalizeOutput: True\n", " txDir: Downlink\n", " filterLen: 16 samples\n", " delayQuantSize: 64\n", " stopBandAtten: 80 dB\n", " dopplerShift: 5 Hz\n", " coherenceTime: 84.628 milliseconds\n", " delaySpread: 300 ns\n", " ueDirAZ: 0°, 90°\n", " Angle Scaling:\n", " Means: 25° 45° 100° 85°\n", " RMS Spreads: 20° 20° 5° 5°\n", " xPolPower: 11.00 dB\n", " angleSpreads: 5° 8° 3° 3°\n", " TX Antenna:\n", " Total Elements: 8\n", " spacing: 0.5𝜆, 0.5𝜆\n", " shape: 2 rows x 4 columns\n", " polarization: |\n", " RX Antenna:\n", " Total Elements: 2\n", " spacing: 0.5𝜆, 0.5𝜆\n", " shape: 1 rows x 2 columns\n", " polarization: |\n", " Orientation (𝛼,𝛃,𝛄): 180° 0° 0°\n", " hasLOS: True\n", " LOS Path:\n", " Delay (ns): 0.00000\n", " Power (dB): -0.20000\n", " AOD (Deg): 0.0\n", " AOA (Deg): -180.0\n", " ZOD (Deg): 98.0\n", " ZOA (Deg): 82.0\n", " NLOS Paths (13):\n", " Delays (ns): 0.000 10.50 183.6 408.9 421.5 541.2 778.8 532.5 1212. 2381. 2827. 2912. 3757.\n", " 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\n", " AODs (Deg): 0 89 89 89 13 13 13 35 -64 -33 53 -132 77 \n", " AOAs (Deg): -180 89 89 89 163 163 163 -137 74 128 -120 -9 -84 \n", " ZODs (Deg): 98 86 86 86 98 98 98 98 88 91 104 80 86 \n", " ZOAs (Deg): 82 87 87 87 79 79 79 78 74 78 87 71 73 \n", "\n" ] } ], "source": [ "# Create a CDL-C channel model with 300 ns delay spread, 4 GHz carrier frequency, and 5 Hz Doppler shift\n", "# with angle scaling\n", "channel = CdlChannel(bwp, 'D', delaySpread=300, carrierFreq=4e9, dopplerShift=5,\n", " txAntenna = AntennaPanel([2,4], polarization=\"|\"), # 8 TX antennas\n", " rxAntenna = AntennaPanel([1,2], polarization=\"|\"), # 2 RX antennas\n", " angleScaling = ([25, 45, 100, 85], # Mean values for AoDs, AoAs, ZoDs, ZoAs\n", " [20, 20, 5, 5])) # Spread values for AoDs, AoAs, ZoDs, ZoAs\n", "print(channel)" ] }, { "cell_type": "markdown", "id": "4009b83f-db3c-4d01-bd7b-9e8617c3045e", "metadata": {}, "source": [ "## Customizing an Existing CDL Profile" ] }, { "cell_type": "code", "execution_count": 4, "id": "8cfd067e-21d0-4daf-88d5-12019757e53d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "CDL-D Channel Properties:\n", " carrierFreq: 4 GHz\n", " normalizeGains: True\n", " normalizeOutput: True\n", " txDir: Downlink\n", " filterLen: 16 samples\n", " delayQuantSize: 64\n", " stopBandAtten: 80 dB\n", " dopplerShift: 5 Hz\n", " coherenceTime: 84.628 milliseconds\n", " delaySpread: 300 ns\n", " ueDirAZ: 0°, 90°\n", " xPolPower: 11.00 dB\n", " angleSpreads: 5° 8° 3° 3°\n", " TX Antenna:\n", " Total Elements: 8\n", " spacing: 0.5𝜆, 0.5𝜆\n", " shape: 2 rows x 4 columns\n", " polarization: |\n", " RX Antenna:\n", " Total Elements: 2\n", " spacing: 0.5𝜆, 0.5𝜆\n", " shape: 1 rows x 2 columns\n", " polarization: |\n", " Orientation (𝛼,𝛃,𝛄): 180° 0° 0°\n", " hasLOS: True\n", " LOS Path:\n", " Delay (ns): 0.00000\n", " Power (dB): -3.20000\n", " AOD (Deg): 0.0\n", " AOA (Deg): -180.0\n", " ZOD (Deg): 98.0\n", " ZOA (Deg): 82.0\n", " NLOS Paths (13):\n", " Delays (ns): 0.000 21.00 367.2 817.8 843.0 1082. 1557. 1065. 2425. 4762. 5654. 5824. 7515.\n", " 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\n", " AODs (Deg): 0 89 89 89 13 13 13 35 -64 -33 53 -132 77 \n", " AOAs (Deg): -180 89 89 89 163 163 163 -137 74 128 -120 -9 -84 \n", " ZODs (Deg): 98 86 86 86 98 98 98 98 88 91 104 80 86 \n", " ZOAs (Deg): 82 87 87 87 79 79 79 78 74 78 87 71 73 \n", "\n" ] } ], "source": [ "# Create a CDL-D channel model where the original delays are doubled and\n", "# the original path powers are attenuated by 3 dB\n", "# Note: If you want to set your own delays:\n", "# - Values must be in normalized form similar to those in TR 38.901,\n", "# Tables 7.7.1-1 to 7.7.1-5. They are scaled using 'delaySpread'.\n", "# - The values must be in increasing order.\n", "delays = CdlChannel.getCdlParams('D', \"delay\") * 2\n", "powers = CdlChannel.getCdlParams('D', \"power\") - 3\n", "channel = CdlChannel(bwp, 'D', delaySpread=300, carrierFreq=4e9, dopplerShift=5,\n", " pathDelays = delays,\n", " pathPowers = powers,\n", " txAntenna = AntennaPanel([2,4], polarization=\"|\"), # 8 TX antennas\n", " rxAntenna = AntennaPanel([1,2], polarization=\"|\")) # 2 RX antennas\n", "print(channel)" ] }, { "cell_type": "markdown", "id": "878a4c6e-fd17-475a-b0f9-eefde62a8ebc", "metadata": {}, "source": [ "## Fully Customized CDL Channel Model" ] }, { "cell_type": "code", "execution_count": 5, "id": "f07e7da7-e075-45f4-ba69-035c5aec2096", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Customized CDL Channel Properties:\n", " carrierFreq: 4 GHz\n", " normalizeGains: True\n", " normalizeOutput: True\n", " txDir: Downlink\n", " filterLen: 16 samples\n", " delayQuantSize: 64\n", " stopBandAtten: 80 dB\n", " dopplerShift: 5 Hz\n", " coherenceTime: 84.628 milliseconds\n", " ueDirAZ: 0°, 90°\n", " xPolPower: 10.00 dB\n", " angleSpreads: 5° 12° 4° 4°\n", " TX Antenna:\n", " Total Elements: 8\n", " spacing: 0.5𝜆, 0.5𝜆\n", " shape: 2 rows x 4 columns\n", " polarization: |\n", " RX Antenna:\n", " Total Elements: 2\n", " spacing: 0.5𝜆, 0.5𝜆\n", " shape: 1 rows x 2 columns\n", " polarization: |\n", " Orientation (𝛼,𝛃,𝛄): 180° 0° 0°\n", " hasLOS: False\n", " NLOS Paths (6):\n", " Delays (ns): 4.000 50.00 120.0 140.0 170.0 200.0\n", " Powers (dB): -1.40 -2.00 -2.20 -4.00 -6.00 -8.20\n", " AODs (Deg): -160 -7 -3 131 -81 148 \n", " AOAs (Deg): 54 152 -14 121 -61 -95 \n", " ZODs (Deg): 92 112 85 70 120 96 \n", " ZOAs (Deg): 95 103 108 84 81 115 \n", "\n" ] } ], "source": [ "# Create a CDL channel model with 6 non-line-of-sight (NLOS) paths:\n", "# Notes:\n", "# - The profile must be set to None to indicate a customized CDL model.\n", "# - The delays must be in increasing order. \n", "# - The delays must be in nanoseconds ('delaySpread' is ignored).\n", "# - Azimuth angles are in the range [-180°, 180°].\n", "# - Zenith angles are in the range [0°, 180°] (90° is horizontal).\n", "# - Path powers are specified in dB.\n", "# - If a line-of-sight (LOS) path exists, the path information should follow the\n", "# CDL rules for specular and Laplacian paths. Refer to TR 38.901, Tables\n", "# 7.7.1-4 and 7.7.1-5 for CDL-D and CDL-E, respectively, and follow those patterns.\n", "# - Set the 'hasLos' argument based on your path information.\n", "delays = np.array([4, 50, 120, 140, 170, 200]) # Delays (ns)\n", "powers = np.array([-1.4, -2, -2.2, -4, -6, -8.2]) # Path powers in dB\n", "aods = np.array([-160, -7, -3, 131, -81, 148]) # Azimuth angles of departure (degrees)\n", "aoas = np.array([54, 152, -14, 121, -61, -95]) # Azimuth angles of arrival (degrees)\n", "zods = np.array([92, 112, 85, 70, 120, 96]) # Zenith angles of departure (degrees)\n", "zoas = np.array([95, 103, 108, 84, 81, 115]) # Zenith angles of arrival (degrees)\n", "\n", "channel = CdlChannel(bwp, profile=None, # Profile is set to None\n", " carrierFreq=4e9, dopplerShift=5,\n", " pathDelays = delays, pathPowers = powers,\n", " aods = aods, aoas = aoas,\n", " zods = zods, zoas = zoas,\n", " hasLos = False,\n", " angleSpreads = [5, 12, 4, 4], # Customized angle spreads\n", " txAntenna = AntennaPanel([2,4], polarization=\"|\"), # 8 TX antennas\n", " rxAntenna = AntennaPanel([1,2], polarization=\"|\")) # 2 RX antennas\n", "\n", "print(channel)" ] }, { "cell_type": "code", "execution_count": 6, "id": "5b938fa2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Time to apply channel in Freq. Domain: 0.003696918487548828\n" ] } ], "source": [ "# Apply the channel in the frequency domain:\n", "t0 =time.time()\n", "channelMatrix = channel.getChannelMatrix()\n", "rxGridF = txGrid.applyChannel(channelMatrix)\n", "t1 =time.time()\n", "print(\"Time to apply channel in Freq. Domain:\", t1-t0)" ] }, { "cell_type": "code", "execution_count": 7, "id": "5beb0236", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Time to apply channel in Time Domain: 0.04989504814147949\n", "NMSE between the rxGrid in Time and Freq. domains: 2.8586347526266583e-15\n" ] } ], "source": [ "# Apply the channel in the time domain and demodulate to obtain a received resource grid (rxGrid)\n", "t0 =time.time()\n", "maxDelay = channel.getMaxDelay() # Calculate the channel maximum delay\n", "paddedTxWaveform = txWaveform.pad(maxDelay) # Pad the waveform with zeros\n", "rxWaveform = channel.applyToSignal(paddedTxWaveform) # Apply the channel to the waveform\n", "offset = channel.getTimingOffset() # Get timing offset for synchronization\n", "syncedWaveform = rxWaveform.sync(offset) # Apply timing synchronization\n", "rxGridT = syncedWaveform.ofdmDemodulate(bwp) # OFDM-demodulate the waveform to obtain a resource grid\n", "t1 =time.time()\n", "print(\"Time to apply channel in Time Domain:\", t1-t0)\n", "print(\"NMSE between the rxGrid in Time and Freq. domains: \", getNmse(rxGridT.grid,rxGridF.grid)) \n" ] }, { "cell_type": "code", "execution_count": null, "id": "0dd446ee-a915-4b7b-8b45-f670bd37f106", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.10" } }, "nbformat": 4, "nbformat_minor": 5 }