Wideband Beamforming#
This guide covers wideband effects in phased arrays and techniques for mitigation, including true time delay (TTD) and hybrid architectures.
Beam Squint Problem#
Phase shifters provide a frequency-independent phase shift. When used for beam steering, the actual time delay varies with frequency, causing the beam to point at different angles across the bandwidth. This is called beam squint.
import phased_array as pa
import numpy as np
# Array at 10 GHz center frequency
wavelength = 0.03 # 10 GHz
geom = pa.create_rectangular_array(32, 32, dx=0.5, dy=0.5, wavelength=wavelength)
# Analyze beam squint for 20% bandwidth
center_freq = 10e9
frequencies = np.linspace(9e9, 11e9, 21)
results = pa.compute_beam_squint(
geom.x, geom.y,
theta0_deg=45, phi0_deg=0,
center_frequency=center_freq,
frequencies=frequencies,
steering_mode='phase'
)
# At 45 deg scan, expect ~1-2 deg squint at band edges
print(f"Squint range: {results['squint'].min():.2f} to {results['squint'].max():.2f} deg")
Squint increases with:
Larger scan angles
Larger array aperture
Greater frequency offset from center
Beam Squint Formula#
Approximate squint for a linear array:
where \(\Delta f\) is frequency offset, \(f_0\) is center frequency, and \(\theta_0\) is scan angle.
Instantaneous Bandwidth#
The instantaneous bandwidth (IBW) is the frequency range over which the beam remains within acceptable pointing error (typically 3 dB beamwidth).
# Analyze IBW for different scan angles
scan_angles = [0, 15, 30, 45, 60]
for angle in scan_angles:
ibw = pa.analyze_instantaneous_bandwidth(
geom.x, geom.y,
theta0_deg=angle,
phi0_deg=0,
center_frequency=10e9,
max_squint_deg=2.0, # Acceptable pointing error
steering_mode='phase'
)
print(f"Scan {angle} deg: IBW = {ibw/1e6:.0f} MHz")
True Time Delay (TTD) Steering#
TTD uses actual time delays instead of phase shifts. Since a time delay produces frequency-proportional phase, the beam points correctly at all frequencies.
# TTD steering vector
weights_ttd = pa.steering_vector_ttd(
geom.x, geom.y,
theta0_deg=45, phi0_deg=0,
frequency=10e9 # Design frequency (any frequency works)
)
# Analyze squint (should be nearly zero)
results_ttd = pa.compute_beam_squint(
geom.x, geom.y, 45, 0, 10e9, frequencies,
steering_mode='ttd'
)
print(f"TTD squint: {np.max(np.abs(results_ttd['squint'])):.3f} deg")
Time Delay Values#
Get the actual time delays required:
delays = pa.steering_delays_ttd(
geom.x, geom.y,
theta0_deg=45, phi0_deg=0
)
# Delays in seconds
print(f"Max delay: {np.max(delays)*1e12:.1f} ps")
print(f"Delay range: {(np.max(delays) - np.min(delays))*1e12:.1f} ps")
TTD vs Phase Steering Comparison#
# Compare patterns at band edges
for f, label in [(9e9, 'Low'), (10e9, 'Center'), (11e9, 'High')]:
k = pa.frequency_to_k(f)
# Phase steering (designed at center frequency)
weights_phase = pa.steering_vector(
pa.frequency_to_k(10e9), geom.x, geom.y, 45, 0
)
# TTD steering
weights_ttd = pa.steering_vector_ttd(geom.x, geom.y, 45, 0, f)
# Compute patterns and compare peak directions
# Phase steering: peak moves with frequency
# TTD: peak stays at 45 deg
Hybrid Phase/TTD Architecture#
Full TTD per element is expensive. A common compromise uses TTD at the subarray level and phase shifters within subarrays.
Creating Hybrid Architecture#
# 64x64 array with 8x8 element subarrays
architecture = pa.create_rectangular_subarrays(
Nx_total=64, Ny_total=64,
Nx_sub=8, Ny_sub=8,
dx=0.5, dy=0.5,
wavelength=wavelength
)
print(f"Total elements: {architecture.geometry.n_elements}") # 4096
print(f"Subarrays: {architecture.n_subarrays}") # 64 (one TTD each)
Hybrid Steering#
# Compute hybrid steering weights
weights_hybrid = pa.steering_vector_hybrid(
architecture,
theta0_deg=45, phi0_deg=0,
frequency=10e9
)
# Subarrays use TTD, elements within use phase
results_hybrid = pa.compute_beam_squint(
architecture.geometry.x, architecture.geometry.y,
45, 0, 10e9, frequencies,
steering_mode='hybrid',
architecture=architecture
)
# Squint is reduced but not eliminated
print(f"Hybrid squint: {np.max(np.abs(results_hybrid['squint'])):.3f} deg")
Subarray Delay Values#
subarray_delays = pa.compute_subarray_delays_ttd(
architecture,
theta0_deg=45, phi0_deg=0
)
print(f"Number of TTD units needed: {len(subarray_delays)}")
print(f"Max subarray delay: {np.max(subarray_delays)*1e12:.1f} ps")
Pattern vs Frequency#
Compute and visualize how the pattern changes across the band:
patterns = pa.compute_pattern_vs_frequency(
geom.x, geom.y,
theta0_deg=30, phi0_deg=0,
center_frequency=10e9,
frequencies=frequencies,
steering_mode='phase',
n_points=361
)
# patterns['frequencies'] - frequency array
# patterns['theta_deg'] - angle array
# patterns['patterns_dB'] - 2D array (n_freq x n_theta)
Visualization#
Plot beam squint:
# Compare all steering modes
fig = pa.plot_beam_squint(
geom.x, geom.y,
theta0_deg=45, phi0_deg=0,
center_frequency=10e9,
bandwidth=2e9,
steering_modes=['phase', 'ttd', 'hybrid'],
architecture=architecture
)
fig.show()
Plot pattern vs frequency:
# Waterfall plot of pattern vs frequency
fig = pa.plot_pattern_vs_frequency_plotly(
geom.x, geom.y,
theta0_deg=30, phi0_deg=0,
center_frequency=10e9,
bandwidth=2e9,
n_freqs=11,
steering_mode='phase'
)
fig.show()
Subarray delay visualization:
fig = pa.plot_subarray_delays(
architecture,
theta0_deg=45, phi0_deg=0
)
fig.show()
Design Guidelines#
Choosing a Steering Approach#
Approach |
Use When |
Considerations |
|---|---|---|
Phase-only |
Narrowband, small apertures, cost-sensitive |
IBW limits operational bandwidth |
Full TTD |
Wideband radar, EW, 5G FR2 |
Expensive, high complexity |
Hybrid |
Moderate bandwidth, large arrays |
Balance of cost and performance |
IBW Requirements#
For a typical 256-element planar array:
Phase-only at 45 deg scan: IBW ~ 2-5% of center frequency
Hybrid (8x8 subarrays): IBW ~ 10-20%
Full TTD: IBW ~ 100% (limited by elements, not steering)
Subarray Sizing#
Larger subarrays = fewer TTD units = more squint remaining
Rule of thumb: Subarray aperture should be small enough that internal squint is less than half the beamwidth: