In [1]:
Copied!
"""Beamforming tutorial."""
"""Beamforming tutorial."""
Out[1]:
'Beamforming tutorial.'
Beamforming.¶
Tutorial Overview:
- Computing Beamformers - Calculate received power with beamforming
- Steering Vectors - Generate steering vectors for different angles
- Beamforming Visualization - Visualize beamforming patterns and performance
Related Video: Beamforming Video
In [2]:
Copied!
import matplotlib.pyplot as plt
import numpy as np
import deepmimo as dm
# Set figure limit to avoid memory warnings
plt.rcParams["figure.max_open_warning"] = 0
import matplotlib.pyplot as plt
import numpy as np
import deepmimo as dm
# Set figure limit to avoid memory warnings
plt.rcParams["figure.max_open_warning"] = 0
In [3]:
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 /home/joao/DeepMIMO/docs/tutorials/deepmimo_scenarios Loading TXRX PAIR: TXset 1 (tx_idx 0) & RXset 0 (rx_idxs 131931)
Computing Beamformers¶
Calculate steering vectors and beamforming gains.
In [4]:
Copied!
# Configure multi-antenna system
ch_params = dm.ChannelParameters()
ch_params.bs_antenna.shape = [8, 1] # 8-element linear array at BS
ch_params.ue_antenna.shape = [1, 1] # Single antenna at UE
# Generate channels
dataset.compute_channels(ch_params)
channels = dataset.channel
print(f"Channel shape: {channels.shape}")
# Configure multi-antenna system
ch_params = dm.ChannelParameters()
ch_params.bs_antenna.shape = [8, 1] # 8-element linear array at BS
ch_params.ue_antenna.shape = [1, 1] # Single antenna at UE
# Generate channels
dataset.compute_channels(ch_params)
channels = dataset.channel
print(f"Channel shape: {channels.shape}")
Generating channels: 0%| | 0/131931 [00:00<?, ?it/s]
Generating channels: 2%|▏ | 2537/131931 [00:00<00:05, 25346.88it/s]
Generating channels: 4%|▍ | 5072/131931 [00:00<00:06, 19605.13it/s]
Generating channels: 5%|▌ | 7111/131931 [00:00<00:07, 16630.30it/s]
Generating channels: 7%|▋ | 8840/131931 [00:00<00:08, 14546.24it/s]
Generating channels: 8%|▊ | 10343/131931 [00:00<00:08, 13879.34it/s]
Generating channels: 9%|▉ | 11754/131931 [00:00<00:09, 13172.17it/s]
Generating channels: 10%|▉ | 13082/131931 [00:00<00:09, 12602.37it/s]
Generating channels: 11%|█ | 14345/131931 [00:01<00:09, 12328.76it/s]
Generating channels: 12%|█▏ | 15577/131931 [00:01<00:09, 12170.82it/s]
Generating channels: 13%|█▎ | 16792/131931 [00:01<00:10, 11494.72it/s]
Generating channels: 14%|█▎ | 17982/131931 [00:01<00:09, 11605.24it/s]
Generating channels: 15%|█▍ | 19145/131931 [00:01<00:09, 11531.55it/s]
Generating channels: 16%|█▌ | 20464/131931 [00:01<00:09, 12001.15it/s]
Generating channels: 17%|█▋ | 22006/131931 [00:01<00:08, 12987.82it/s]
Generating channels: 18%|█▊ | 23573/131931 [00:01<00:07, 13770.00it/s]
Generating channels: 19%|█▉ | 25483/131931 [00:01<00:06, 15338.95it/s]
Generating channels: 21%|██ | 27545/131931 [00:01<00:06, 16900.55it/s]
Generating channels: 22%|██▏ | 29618/131931 [00:02<00:05, 18035.09it/s]
Generating channels: 24%|██▍ | 32024/131931 [00:02<00:05, 19828.95it/s]
Generating channels: 26%|██▌ | 34540/131931 [00:02<00:04, 21418.72it/s]
Generating channels: 28%|██▊ | 36922/131931 [00:02<00:04, 22133.33it/s]
Generating channels: 30%|██▉ | 39439/131931 [00:02<00:04, 23037.37it/s]
Generating channels: 32%|███▏ | 41832/131931 [00:02<00:03, 23302.54it/s]
Generating channels: 33%|███▎ | 44165/131931 [00:02<00:03, 22371.06it/s]
Generating channels: 35%|███▌ | 46412/131931 [00:02<00:04, 20796.87it/s]
Generating channels: 37%|███▋ | 48518/131931 [00:02<00:04, 20782.50it/s]
Generating channels: 39%|███▉ | 51358/131931 [00:02<00:03, 22934.17it/s]
Generating channels: 41%|████ | 54418/131931 [00:03<00:03, 25141.40it/s]
Generating channels: 43%|████▎ | 56959/131931 [00:03<00:03, 24947.17it/s]
Generating channels: 45%|████▌ | 59472/131931 [00:03<00:02, 24633.25it/s]
Generating channels: 47%|████▋ | 61949/131931 [00:03<00:02, 23418.74it/s]
Generating channels: 49%|████▊ | 64311/131931 [00:03<00:03, 20728.52it/s]
Generating channels: 50%|█████ | 66447/131931 [00:03<00:03, 19172.29it/s]
Generating channels: 52%|█████▏ | 68420/131931 [00:03<00:03, 18001.48it/s]
Generating channels: 53%|█████▎ | 70262/131931 [00:03<00:03, 17598.66it/s]
Generating channels: 55%|█████▍ | 72048/131931 [00:04<00:03, 16894.50it/s]
Generating channels: 56%|█████▌ | 73754/131931 [00:04<00:03, 16501.86it/s]
Generating channels: 57%|█████▋ | 75581/131931 [00:04<00:03, 16973.67it/s]
Generating channels: 59%|█████▉ | 77931/131931 [00:04<00:02, 18775.59it/s]
Generating channels: 61%|██████ | 79832/131931 [00:04<00:02, 18532.68it/s]
Generating channels: 62%|██████▏ | 81738/131931 [00:04<00:02, 18681.38it/s]
Generating channels: 63%|██████▎ | 83619/131931 [00:04<00:02, 18561.87it/s]
Generating channels: 65%|██████▍ | 85609/131931 [00:04<00:02, 18948.02it/s]
Generating channels: 66%|██████▋ | 87511/131931 [00:04<00:02, 18868.45it/s]
Generating channels: 68%|██████▊ | 89403/131931 [00:04<00:02, 18530.39it/s]
Generating channels: 69%|██████▉ | 91261/131931 [00:05<00:02, 18195.81it/s]
Generating channels: 71%|███████ | 93085/131931 [00:05<00:02, 17001.55it/s]
Generating channels: 72%|███████▏ | 94801/131931 [00:05<00:02, 16386.31it/s]
Generating channels: 73%|███████▎ | 96453/131931 [00:05<00:02, 15747.61it/s]
Generating channels: 74%|███████▍ | 98038/131931 [00:05<00:02, 14318.95it/s]
Generating channels: 75%|███████▌ | 99495/131931 [00:05<00:02, 13406.10it/s]
Generating channels: 76%|███████▋ | 100857/131931 [00:05<00:02, 12931.83it/s]
Generating channels: 77%|███████▋ | 102164/131931 [00:05<00:02, 12619.86it/s]
Generating channels: 78%|███████▊ | 103434/131931 [00:06<00:02, 12334.23it/s]
Generating channels: 79%|███████▉ | 104671/131931 [00:06<00:02, 12129.49it/s]
Generating channels: 80%|████████ | 105886/131931 [00:06<00:02, 12015.12it/s]
Generating channels: 81%|████████ | 107088/131931 [00:06<00:02, 11997.90it/s]
Generating channels: 82%|████████▏ | 108525/131931 [00:06<00:01, 12674.32it/s]
Generating channels: 84%|████████▎ | 110224/131931 [00:06<00:01, 13925.07it/s]
Generating channels: 85%|████████▌ | 112163/131931 [00:06<00:01, 15523.57it/s]
Generating channels: 87%|████████▋ | 114512/131931 [00:06<00:00, 17871.73it/s]
Generating channels: 89%|████████▉ | 117139/131931 [00:06<00:00, 20357.22it/s]
Generating channels: 91%|█████████ | 119465/131931 [00:06<00:00, 21220.22it/s]
Generating channels: 92%|█████████▏| 121595/131931 [00:07<00:00, 20872.75it/s]
Generating channels: 94%|█████████▍| 123689/131931 [00:07<00:00, 20511.85it/s]
Generating channels: 95%|█████████▌| 125876/131931 [00:07<00:00, 20904.70it/s]
Generating channels: 97%|█████████▋| 128115/131931 [00:07<00:00, 21341.68it/s]
Generating channels: 99%|█████████▉| 130945/131931 [00:07<00:00, 23401.22it/s]
Generating channels: 100%|██████████| 131931/131931 [00:07<00:00, 17591.39it/s]
Channel shape: (131931, 1, 8, 1)
Steering Vectors¶
Generate steering vectors for specific angles.
In [5]:
Copied!
# Array parameters
num_antennas = ch_params.bs_antenna.shape[0]
antenna_spacing = ch_params.bs_antenna.spacing # in wavelengths
# Generate steering vector for a specific angle
theta = 30 # degrees
sv = dm.steering_vec(ch_params.bs_antenna.shape, phi=theta).squeeze()
print(f"Steering vector shape: {sv.shape}")
print(f"Steering vector for {theta}°: {sv[:4]}...")
# Array parameters
num_antennas = ch_params.bs_antenna.shape[0]
antenna_spacing = ch_params.bs_antenna.spacing # in wavelengths
# Generate steering vector for a specific angle
theta = 30 # degrees
sv = dm.steering_vec(ch_params.bs_antenna.shape, phi=theta).squeeze()
print(f"Steering vector shape: {sv.shape}")
print(f"Steering vector for {theta}°: {sv[:4]}...")
Steering vector shape: (8,) Steering vector for 30°: [ 3.53553391e-01+0.00000000e+00j 1.00153524e-16+3.53553391e-01j -3.53553391e-01+2.00307049e-16j -3.78965196e-16-3.53553391e-01j]...
Steering Vectors for Multiple Angles¶
In [6]:
Copied!
# Generate steering vectors for a range of angles
angles = np.arange(-90, 91, 5) # -90° to 90° in 5° steps
steering_vectors = np.array(
[dm.steering_vec(ch_params.bs_antenna.shape, phi=angle).squeeze() for angle in angles]
)
print(f"Steering vectors shape: {steering_vectors.shape}")
# Generate steering vectors for a range of angles
angles = np.arange(-90, 91, 5) # -90° to 90° in 5° steps
steering_vectors = np.array(
[dm.steering_vec(ch_params.bs_antenna.shape, phi=angle).squeeze() for angle in angles]
)
print(f"Steering vectors shape: {steering_vectors.shape}")
Steering vectors shape: (37, 8)
Beamforming Gain¶
In [7]:
Copied!
# Compute beamforming gain for a specific user
user_idx = 10
h = channels[user_idx, 0, :, 0] # Channel vector (RX ant 0, all TX ants, path 0)
# Matched filter beamformer (MF)
h_norm = np.linalg.norm(h)
if h_norm > 0:
w_mf = h.conj() / h_norm
# Compute received power with beamforming
bf_gain = np.abs(np.dot(w_mf.conj(), h)) ** 2
no_bf_power = np.sum(np.abs(h) ** 2) / num_antennas
print(f"Power without beamforming: {10 * np.log10(no_bf_power + 1e-12):.2f} dB")
print(f"Power with beamforming: {10 * np.log10(bf_gain + 1e-12):.2f} dB")
print(f"Beamforming gain: {10 * np.log10(bf_gain / (no_bf_power + 1e-12)):.2f} dB")
else:
print("Channel has zero power, skipping beamforming gain calculation")
# Compute beamforming gain for a specific user
user_idx = 10
h = channels[user_idx, 0, :, 0] # Channel vector (RX ant 0, all TX ants, path 0)
# Matched filter beamformer (MF)
h_norm = np.linalg.norm(h)
if h_norm > 0:
w_mf = h.conj() / h_norm
# Compute received power with beamforming
bf_gain = np.abs(np.dot(w_mf.conj(), h)) ** 2
no_bf_power = np.sum(np.abs(h) ** 2) / num_antennas
print(f"Power without beamforming: {10 * np.log10(no_bf_power + 1e-12):.2f} dB")
print(f"Power with beamforming: {10 * np.log10(bf_gain + 1e-12):.2f} dB")
print(f"Beamforming gain: {10 * np.log10(bf_gain / (no_bf_power + 1e-12)):.2f} dB")
else:
print("Channel has zero power, skipping beamforming gain calculation")
Power without beamforming: -120.00 dB Power with beamforming: -120.00 dB Beamforming gain: -51.97 dB
Beam Patterns¶
Array Factor Pattern¶
In [8]:
Copied!
# Compute array factor for different angles
array_factor = []
for angle in angles:
sv = dm.steering_vec(ch_params.bs_antenna.shape, phi=angle).squeeze()
# Beamformer pointed at 0 degrees
w = dm.steering_vec(ch_params.bs_antenna.shape, phi=0).squeeze()
af = np.abs(np.dot(w.conj(), sv))
array_factor.append(af)
array_factor = np.array(array_factor)
# Plot array factor in dB
plt.figure(figsize=(10, 6))
plt.plot(angles, 20 * np.log10(array_factor / np.max(array_factor)))
plt.xlabel("Angle (degrees)")
plt.ylabel("Array Factor (dB)")
plt.title("Beamforming Array Factor Pattern")
plt.grid(visible=True)
plt.ylim([-40, 0])
plt.show()
# Compute array factor for different angles
array_factor = []
for angle in angles:
sv = dm.steering_vec(ch_params.bs_antenna.shape, phi=angle).squeeze()
# Beamformer pointed at 0 degrees
w = dm.steering_vec(ch_params.bs_antenna.shape, phi=0).squeeze()
af = np.abs(np.dot(w.conj(), sv))
array_factor.append(af)
array_factor = np.array(array_factor)
# Plot array factor in dB
plt.figure(figsize=(10, 6))
plt.plot(angles, 20 * np.log10(array_factor / np.max(array_factor)))
plt.xlabel("Angle (degrees)")
plt.ylabel("Array Factor (dB)")
plt.title("Beamforming Array Factor Pattern")
plt.grid(visible=True)
plt.ylim([-40, 0])
plt.show()
Polar Plot¶
In [9]:
Copied!
# Polar plot of beam pattern
fig, ax = plt.subplots(subplot_kw={"projection": "polar"}, figsize=(8, 8))
angles_rad = np.deg2rad(angles)
ax.plot(angles_rad, array_factor)
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_title("Beam Pattern (Polar)")
ax.grid(visible=True)
plt.show()
# Polar plot of beam pattern
fig, ax = plt.subplots(subplot_kw={"projection": "polar"}, figsize=(8, 8))
angles_rad = np.deg2rad(angles)
ax.plot(angles_rad, array_factor)
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_title("Beam Pattern (Polar)")
ax.grid(visible=True)
plt.show()
Beamforming Visualization¶
Received Power per Beam¶
In [10]:
Copied!
# Scan through all angles and compute received power
num_users = min(100, len(dataset.power)) # Use first 100 users
beam_powers = np.zeros((num_users, len(angles)))
for user_idx in range(num_users):
h = channels[user_idx, 0, :, 0] # Channel vector (RX ant 0, all TX ants, path 0)
for angle_idx, angle in enumerate(angles):
w = dm.steering_vec(ch_params.bs_antenna.shape, phi=angle).squeeze()
power = np.abs(np.dot(w.conj(), h)) ** 2
beam_powers[user_idx, angle_idx] = power
# Plot heatmap
plt.figure(figsize=(12, 6))
plt.imshow(
10 * np.log10(beam_powers + 1e-10),
aspect="auto",
cmap="hot",
origin="lower",
extent=[angles[0], angles[-1], 0, num_users],
)
plt.colorbar(label="Received Power (dBW)")
plt.xlabel("Beam Angle (degrees)")
plt.ylabel("User Index")
plt.title("Received Power per Beam and User")
plt.show()
# Scan through all angles and compute received power
num_users = min(100, len(dataset.power)) # Use first 100 users
beam_powers = np.zeros((num_users, len(angles)))
for user_idx in range(num_users):
h = channels[user_idx, 0, :, 0] # Channel vector (RX ant 0, all TX ants, path 0)
for angle_idx, angle in enumerate(angles):
w = dm.steering_vec(ch_params.bs_antenna.shape, phi=angle).squeeze()
power = np.abs(np.dot(w.conj(), h)) ** 2
beam_powers[user_idx, angle_idx] = power
# Plot heatmap
plt.figure(figsize=(12, 6))
plt.imshow(
10 * np.log10(beam_powers + 1e-10),
aspect="auto",
cmap="hot",
origin="lower",
extent=[angles[0], angles[-1], 0, num_users],
)
plt.colorbar(label="Received Power (dBW)")
plt.xlabel("Beam Angle (degrees)")
plt.ylabel("User Index")
plt.title("Received Power per Beam and User")
plt.show()
Best Beam per Position¶
In [11]:
Copied!
# Find best beam for each user
best_beams = np.argmax(beam_powers, axis=1)
best_angles = angles[best_beams]
plt.figure(figsize=(10, 6))
plt.scatter(
dataset.rx_pos[:num_users, 0],
dataset.rx_pos[:num_users, 1],
c=best_angles,
cmap="viridis",
s=20,
)
plt.colorbar(label="Best Beam Angle (degrees)")
plt.xlabel("X (m)")
plt.ylabel("Y (m)")
plt.title("Best Beam Angle per User Position")
plt.grid(visible=True)
plt.show()
# Find best beam for each user
best_beams = np.argmax(beam_powers, axis=1)
best_angles = angles[best_beams]
plt.figure(figsize=(10, 6))
plt.scatter(
dataset.rx_pos[:num_users, 0],
dataset.rx_pos[:num_users, 1],
c=best_angles,
cmap="viridis",
s=20,
)
plt.colorbar(label="Best Beam Angle (degrees)")
plt.xlabel("X (m)")
plt.ylabel("Y (m)")
plt.title("Best Beam Angle per User Position")
plt.grid(visible=True)
plt.show()
Max Received Power Map¶
In [12]:
Copied!
# Maximum received power (best beam) for each user
max_powers = np.max(beam_powers, axis=1)
plt.figure(figsize=(10, 6))
plt.scatter(
dataset.rx_pos[:num_users, 0],
dataset.rx_pos[:num_users, 1],
c=10 * np.log10(max_powers + 1e-10),
cmap="viridis",
s=20,
)
plt.colorbar(label="Max Received Power (dBW)")
plt.xlabel("X (m)")
plt.ylabel("Y (m)")
plt.title("Maximum Received Power with Beamforming")
plt.grid(visible=True)
plt.show()
# Maximum received power (best beam) for each user
max_powers = np.max(beam_powers, axis=1)
plt.figure(figsize=(10, 6))
plt.scatter(
dataset.rx_pos[:num_users, 0],
dataset.rx_pos[:num_users, 1],
c=10 * np.log10(max_powers + 1e-10),
cmap="viridis",
s=20,
)
plt.colorbar(label="Max Received Power (dBW)")
plt.xlabel("X (m)")
plt.ylabel("Y (m)")
plt.title("Maximum Received Power with Beamforming")
plt.grid(visible=True)
plt.show()
Multi-User Beamforming¶
In [13]:
Copied!
# Zero-forcing beamforming for multiple users
num_selected_users = min(num_antennas - 1, num_users)
rng = np.random.default_rng()
selected_users = rng.choice(num_users, num_selected_users, replace=False)
# Channel matrix for selected users
H = np.array([channels[u, 0, :, 0] for u in selected_users]).T
# Zero-forcing precoder
W_zf = np.linalg.pinv(H)
print(f"Channel matrix H shape: {H.shape}")
print(f"Zero-forcing precoder W shape: {W_zf.shape}")
# Zero-forcing beamforming for multiple users
num_selected_users = min(num_antennas - 1, num_users)
rng = np.random.default_rng()
selected_users = rng.choice(num_users, num_selected_users, replace=False)
# Channel matrix for selected users
H = np.array([channels[u, 0, :, 0] for u in selected_users]).T
# Zero-forcing precoder
W_zf = np.linalg.pinv(H)
print(f"Channel matrix H shape: {H.shape}")
print(f"Zero-forcing precoder W shape: {W_zf.shape}")
Channel matrix H shape: (8, 7) Zero-forcing precoder W shape: (7, 8)
Beamforming Gain Analysis¶
In [14]:
Copied!
# Compare beamforming techniques
gains_mf = []
gains_uniform = []
for user_idx in range(num_users):
h = channels[user_idx, 0, :, 0]
# Matched filter
h_norm = np.linalg.norm(h)
if h_norm > 0:
w_mf = h.conj() / h_norm
gain_mf = np.abs(np.dot(w_mf.conj(), h)) ** 2
else:
gain_mf = 0.0
# Uniform weighting
w_uniform = np.ones(num_antennas) / np.sqrt(num_antennas)
gain_uniform = np.abs(np.dot(w_uniform.conj(), h)) ** 2
gains_mf.append(gain_mf)
gains_uniform.append(gain_uniform)
# Plot comparison
plt.figure(figsize=(10, 6))
epsilon = 1e-10 # Prevent log10(0)
plt.hist(
[10 * np.log10(np.array(gains_uniform) + epsilon), 10 * np.log10(np.array(gains_mf) + epsilon)],
bins=30,
label=["Uniform", "Matched Filter"],
alpha=0.7,
)
plt.xlabel("Received Power (dBW)")
plt.ylabel("Count")
plt.title("Beamforming Gain Comparison")
plt.legend()
plt.grid(visible=True)
plt.show()
# Compare beamforming techniques
gains_mf = []
gains_uniform = []
for user_idx in range(num_users):
h = channels[user_idx, 0, :, 0]
# Matched filter
h_norm = np.linalg.norm(h)
if h_norm > 0:
w_mf = h.conj() / h_norm
gain_mf = np.abs(np.dot(w_mf.conj(), h)) ** 2
else:
gain_mf = 0.0
# Uniform weighting
w_uniform = np.ones(num_antennas) / np.sqrt(num_antennas)
gain_uniform = np.abs(np.dot(w_uniform.conj(), h)) ** 2
gains_mf.append(gain_mf)
gains_uniform.append(gain_uniform)
# Plot comparison
plt.figure(figsize=(10, 6))
epsilon = 1e-10 # Prevent log10(0)
plt.hist(
[10 * np.log10(np.array(gains_uniform) + epsilon), 10 * np.log10(np.array(gains_mf) + epsilon)],
bins=30,
label=["Uniform", "Matched Filter"],
alpha=0.7,
)
plt.xlabel("Received Power (dBW)")
plt.ylabel("Count")
plt.title("Beamforming Gain Comparison")
plt.legend()
plt.grid(visible=True)
plt.show()
Next Steps¶
Continue with:
- Tutorial 7: Convert & Upload Ray-tracing dataset - Work with external ray tracers
- Tutorial 8: Migration Guide - Migrating from DeepMIMO v3 to v4