Detailed Channel Generation.¶
Tutorial Overview:
- Channel Parameters - Configuring channel generation
- Time Domain - Generate time-domain channel responses
- Frequency Domain (OFDM) - Generate OFDM channel responses
- Antenna Rotation - Adjust antenna orientations
Related Video: Channel Generation Video
In [1]:
Copied!
import matplotlib.pyplot as plt
import numpy as np
import deepmimo as dm
import matplotlib.pyplot as plt
import numpy as np
import deepmimo as dm
Warning: AODT features require pandas/pyarrow. Install with: pip install 'deepmimo[aodt]'
In [2]:
Copied!
# Load dataset
scen_name = "asu_campus_3p5"
dm.download(scen_name)
dataset = dm.load(scen_name)
# Load dataset
scen_name = "asu_campus_3p5"
dm.download(scen_name)
dataset = dm.load(scen_name)
Scenario "asu_campus_3p5" already exists in /Users/namhyunk/Documents/GitHub/DeepMIMO/deepmimo_scenarios Loading TXRX PAIR: TXset 1 (tx_idx 0) & RXset 0 (rx_idxs 131931)
Channel Parameters¶
Configure channel generation with custom parameters.
In [3]:
Copied!
# Get help on channel parameters
dm.info("ch_params")
# Get help on channel parameters
dm.info("ch_params")
Channel Generation Parameters: ============================== bs_antenna: Base station antenna array configuration parameters. ue_antenna: User equipment antenna array configuration parameters. bs_antenna.shape: Antenna array dimensions [X, Y] or [X, Y, Z] elements Default: [1, 1] | Type: list[int] | Units: number of elements bs_antenna.spacing: Spacing between antenna elements Default: 0.5 | Type: float | Units: wavelengths bs_antenna.rotation: Rotation angles [azimuth, elevation, polarization] Default: [0, 0, 0] | Type: list[float] | Units: degrees bs_antenna.radiation_pattern: Antenna element radiation pattern Default: "isotropic" | Type: str | Options: "isotropic", "halfwave-dipole" doppler: Enable/disable Doppler effect simulation Default: False | Type: bool num_paths: Maximum number of paths to consider per user Default: 10 | Type: int | Units: number of paths freq_domain: Channel domain Default: 0 | Type: int | Options: 0 (time domain), 1 (frequency domain/OFDM) ofdm: OFDM channel configuration parameters. Used (and needed!) only if freq_domain=1. Default: None | Type: dict ofdm.bandwidth: System bandwidth Default: 10e6 | Type: float | Units: Hz ofdm.subcarriers: Total number of OFDM subcarriers Default: 512 | Type: int | Units: number of subcarriers ofdm.selected_subcarriers: Indices of subcarriers to generate Default: None (all subcarriers) | Type: list[int] | Units: subcarrier indices ofdm.rx_filter: Enable/disable receive low-pass filter / ADC filter Default: False | Type: bool
In [4]:
Copied!
# Create channel parameters
ch_params = dm.ChannelParameters()
print("Default channel parameters:")
print(ch_params)
# Create channel parameters
ch_params = dm.ChannelParameters()
print("Default channel parameters:")
print(ch_params)
Default channel parameters:
{'bs_antenna': {'radiation_pattern': 'isotropic',
'rotation': array([0, 0, 0]),
'shape': array([8, 1]),
'spacing': 0.5},
'doppler': 0,
'freq_domain': 1,
'num_paths': 25,
'ofdm': {'bandwidth': 10000000.0,
'rx_filter': 0,
'selected_subcarriers': array([0]),
'subcarriers': 512},
'ue_antenna': {'radiation_pattern': 'isotropic',
'rotation': array([0, 0, 0]),
'shape': array([1, 1]),
'spacing': 0.5}}
In [5]:
Copied!
# Customize antenna array configuration
ch_params.bs_antenna.shape = [4, 4] # 4x4 BS antenna array
ch_params.bs_antenna.spacing = 0.5 # Half-wavelength spacing
ch_params.ue_antenna.shape = [2, 1] # 2x1 UE antenna array
print(f"BS antenna shape: {ch_params.bs_antenna.shape}")
print(f"UE antenna shape: {ch_params.ue_antenna.shape}")
# Customize antenna array configuration
ch_params.bs_antenna.shape = [4, 4] # 4x4 BS antenna array
ch_params.bs_antenna.spacing = 0.5 # Half-wavelength spacing
ch_params.ue_antenna.shape = [2, 1] # 2x1 UE antenna array
print(f"BS antenna shape: {ch_params.bs_antenna.shape}")
print(f"UE antenna shape: {ch_params.ue_antenna.shape}")
BS antenna shape: [4, 4] UE antenna shape: [2, 1]
In [6]:
Copied!
# Set the number of paths to use
ch_params.num_paths = 20
print(f"Number of paths: {ch_params.num_paths}")
# Set the number of paths to use
ch_params.num_paths = 20
print(f"Number of paths: {ch_params.num_paths}")
Number of paths: 20
Time Domain¶
Generate time-domain channel responses.
In [7]:
Copied!
# Set to time domain
ch_params.freq_domain = False
# Generate time-domain channel
dataset.compute_channels(ch_params)
time_channels = dataset.channel
print(f"Time domain channel shape: {time_channels.shape}")
print("Shape: [num_rx, num_rx_ant, num_tx_ant, num_paths]")
# Set to time domain
ch_params.freq_domain = False
# Generate time-domain channel
dataset.compute_channels(ch_params)
time_channels = dataset.channel
print(f"Time domain channel shape: {time_channels.shape}")
print("Shape: [num_rx, num_rx_ant, num_tx_ant, num_paths]")
Time domain channel shape: (131931, 2, 16, 10) Shape: [num_rx, num_rx_ant, num_tx_ant, num_paths]
In [8]:
Copied!
# Visualize channel impulse response for one user
user_idx = 10
cir = time_channels[user_idx, 0, 0, :] # First RX ant, first TX ant
plt.figure(figsize=(10, 5))
plt.stem(np.abs(cir))
plt.xlabel("Path Index")
plt.ylabel("|h|")
plt.title(f"Channel Impulse Response - User {user_idx}")
plt.grid(visible=True)
plt.show()
# Visualize channel impulse response for one user
user_idx = 10
cir = time_channels[user_idx, 0, 0, :] # First RX ant, first TX ant
plt.figure(figsize=(10, 5))
plt.stem(np.abs(cir))
plt.xlabel("Path Index")
plt.ylabel("|h|")
plt.title(f"Channel Impulse Response - User {user_idx}")
plt.grid(visible=True)
plt.show()
Frequency Domain (OFDM)¶
Generate OFDM channel responses.
In [9]:
Copied!
# Configure OFDM parameters
ch_params.freq_domain = True
ch_params.ofdm.bandwidth = 10e6 # 10 MHz
ch_params.ofdm.subcarriers = 512
ch_params.ofdm.selected_subcarriers = np.arange(64) # Select first 64 subcarriers
ch_params.ofdm.rx_filter = False # No low-pass filter
# Generate frequency-domain channel
dataset.compute_channels(ch_params)
freq_channels = dataset.channel
print(f"Frequency domain channel shape: {freq_channels.shape}")
print("Shape: [num_rx, num_rx_ant, num_tx_ant, num_subcarriers]")
# Configure OFDM parameters
ch_params.freq_domain = True
ch_params.ofdm.bandwidth = 10e6 # 10 MHz
ch_params.ofdm.subcarriers = 512
ch_params.ofdm.selected_subcarriers = np.arange(64) # Select first 64 subcarriers
ch_params.ofdm.rx_filter = False # No low-pass filter
# Generate frequency-domain channel
dataset.compute_channels(ch_params)
freq_channels = dataset.channel
print(f"Frequency domain channel shape: {freq_channels.shape}")
print("Shape: [num_rx, num_rx_ant, num_tx_ant, num_subcarriers]")
Frequency domain channel shape: (131931, 2, 16, 64) Shape: [num_rx, num_rx_ant, num_tx_ant, num_subcarriers]
In [10]:
Copied!
# Visualize frequency response for one user
user_idx = 10
freq_response = freq_channels[user_idx, 0, 0, :] # First RX ant, first TX ant
plt.figure(figsize=(10, 5))
plt.plot(np.abs(freq_response))
plt.xlabel("Subcarrier Index")
plt.ylabel("|H[k]|")
plt.title(f"Channel Frequency Response - User {user_idx}")
plt.grid(visible=True)
plt.show()
# Visualize frequency response for one user
user_idx = 10
freq_response = freq_channels[user_idx, 0, 0, :] # First RX ant, first TX ant
plt.figure(figsize=(10, 5))
plt.plot(np.abs(freq_response))
plt.xlabel("Subcarrier Index")
plt.ylabel("|H[k]|")
plt.title(f"Channel Frequency Response - User {user_idx}")
plt.grid(visible=True)
plt.show()
Sampling Subcarriers¶
You can sample only specific subcarriers for efficiency.
In [11]:
Copied!
# Sample every 4th subcarrier
ch_params.ofdm.selected_subcarriers = list(range(0, 512, 4))
dataset.compute_channels(ch_params)
freq_channels_sampled = dataset.channel
print(f"Sampled frequency domain channel shape: {freq_channels_sampled.shape}")
# Sample every 4th subcarrier
ch_params.ofdm.selected_subcarriers = list(range(0, 512, 4))
dataset.compute_channels(ch_params)
freq_channels_sampled = dataset.channel
print(f"Sampled frequency domain channel shape: {freq_channels_sampled.shape}")
Sampled frequency domain channel shape: (131931, 2, 16, 128)
Antenna Rotation¶
Adjust antenna orientations to simulate different deployment scenarios.
Azimuth Rotation¶
In [12]:
Copied!
# Rotate BS antennas in azimuth
ch_params.bs_antenna.rotation = [45, 0, 0] # 45° azimuth rotation
dataset.compute_channels(ch_params)
rotated_channels = dataset.channel
print(f"Channels with rotated BS antennas shape: {rotated_channels.shape}")
# Rotate BS antennas in azimuth
ch_params.bs_antenna.rotation = [45, 0, 0] # 45° azimuth rotation
dataset.compute_channels(ch_params)
rotated_channels = dataset.channel
print(f"Channels with rotated BS antennas shape: {rotated_channels.shape}")
Channels with rotated BS antennas shape: (131931, 2, 16, 128)
In [13]:
Copied!
# Compare power before and after rotation
ch_params.bs_antenna.rotation = [0, 0, 0]
dataset.compute_channels(ch_params)
channels_no_rot = dataset.channel
ch_params.bs_antenna.rotation = [90, 0, 0]
dataset.compute_channels(ch_params)
channels_rot = dataset.channel
power_no_rot = np.mean(np.abs(channels_no_rot[0:100]) ** 2)
power_rot = np.mean(np.abs(channels_rot[0:100]) ** 2)
print(f"Average power (no rotation): {power_no_rot:.6f}")
print(f"Average power (90° rotation): {power_rot:.6f}")
# Compare power before and after rotation
ch_params.bs_antenna.rotation = [0, 0, 0]
dataset.compute_channels(ch_params)
channels_no_rot = dataset.channel
ch_params.bs_antenna.rotation = [90, 0, 0]
dataset.compute_channels(ch_params)
channels_rot = dataset.channel
power_no_rot = np.mean(np.abs(channels_no_rot[0:100]) ** 2)
power_rot = np.mean(np.abs(channels_rot[0:100]) ** 2)
print(f"Average power (no rotation): {power_no_rot:.6f}")
print(f"Average power (90° rotation): {power_rot:.6f}")
Average power (no rotation): 0.000000 Average power (90° rotation): 0.000000
Elevation Rotation¶
In [14]:
Copied!
# Rotate BS antennas in elevation
ch_params.bs_antenna.rotation = [0, 15, 0] # 15° elevation (downtilt)
dataset.compute_channels(ch_params)
tilted_channels = dataset.channel
print(f"Channels with tilted BS antennas shape: {tilted_channels.shape}")
# Rotate BS antennas in elevation
ch_params.bs_antenna.rotation = [0, 15, 0] # 15° elevation (downtilt)
dataset.compute_channels(ch_params)
tilted_channels = dataset.channel
print(f"Channels with tilted BS antennas shape: {tilted_channels.shape}")
Channels with tilted BS antennas shape: (131931, 2, 16, 128)
Channel Comparison¶
Compare different antenna configurations.
In [15]:
Copied!
# Single antenna vs. array
ch_params_single = dm.ChannelParameters()
ch_params_single.bs_antenna.shape = [1, 1]
ch_params_single.ue_antenna.shape = [1, 1]
ch_params_array = dm.ChannelParameters()
ch_params_array.bs_antenna.shape = [8, 1]
ch_params_array.ue_antenna.shape = [4, 1]
dataset.compute_channels(ch_params_single)
channels_single = dataset.channel
dataset.compute_channels(ch_params_array)
channels_array = dataset.channel
print(f"Single antenna channel shape: {channels_single.shape}")
print(f"Array antenna channel shape: {channels_array.shape}")
# Single antenna vs. array
ch_params_single = dm.ChannelParameters()
ch_params_single.bs_antenna.shape = [1, 1]
ch_params_single.ue_antenna.shape = [1, 1]
ch_params_array = dm.ChannelParameters()
ch_params_array.bs_antenna.shape = [8, 1]
ch_params_array.ue_antenna.shape = [4, 1]
dataset.compute_channels(ch_params_single)
channels_single = dataset.channel
dataset.compute_channels(ch_params_array)
channels_array = dataset.channel
print(f"Single antenna channel shape: {channels_single.shape}")
print(f"Array antenna channel shape: {channels_array.shape}")
Single antenna channel shape: (131931, 1, 1, 1) Array antenna channel shape: (131931, 4, 8, 1)
Next Steps¶
Continue with:
- Tutorial 4: User Selection and Dataset Manipulation - Learn how to filter and sample users
- Tutorial 5: Doppler and Mobility - Add time-varying effects to your channels