Beamforming Module#
The beamforming module provides amplitude tapering functions for sidelobe control, null steering for interference rejection, and multi-beam synthesis.
Amplitude Tapers (1D)#
- phased_array.taylor_taper_1d(n, sidelobe_dB=-30.0, nbar=4)[source]#
Generate 1D Taylor window for sidelobe control.
- phased_array.chebyshev_taper_1d(n, sidelobe_dB=-30.0)[source]#
Generate 1D Dolph-Chebyshev window for equi-ripple sidelobes.
Amplitude Tapers (2D)#
- phased_array.taylor_taper_2d(Nx, Ny, sidelobe_dB=-30.0, nbar=4)[source]#
Generate 2D Taylor window (separable product).
- Parameters:
- Returns:
taper – 2D amplitude taper (Nx, Ny), flattened row-major
- Return type:
ndarray
Examples
Apply Taylor taper for -30 dB sidelobes:
>>> import phased_array as pa >>> taper = pa.taylor_taper_2d(16, 16, sidelobe_dB=-30) >>> taper.shape (256,)
Combine with steering weights:
>>> geom = pa.create_rectangular_array(16, 16, dx=0.5, dy=0.5) >>> k = pa.wavelength_to_k(1.0) >>> weights = pa.steering_vector(k, geom.x, geom.y, theta0_deg=30, phi0_deg=0) >>> weights_tapered = weights * pa.taylor_taper_2d(16, 16, sidelobe_dB=-35)
Compare taper efficiency:
>>> taper = pa.taylor_taper_2d(16, 16, sidelobe_dB=-40) >>> efficiency = pa.compute_taper_efficiency(taper) >>> efficiency < 1.0 # Tapering reduces efficiency True
- phased_array.chebyshev_taper_2d(Nx, Ny, sidelobe_dB=-30.0)[source]#
Generate 2D Chebyshev window (separable product).
Taper Analysis#
- phased_array.compute_taper_efficiency(taper)[source]#
Compute aperture efficiency for a taper.
Efficiency = (sum of weights)^2 / (N * sum of weights^2) Uniform illumination gives 100% efficiency.
- Parameters:
taper (
ndarray) – Amplitude taper weights- Returns:
efficiency – Aperture efficiency (0 to 1)
- Return type:
- phased_array.compute_taper_directivity_loss(taper)[source]#
Compute directivity loss due to tapering in dB.
- Parameters:
taper (
ndarray) – Amplitude taper weights- Returns:
loss_dB – Directivity loss relative to uniform illumination (negative or zero)
- Return type:
- phased_array.apply_taper_to_geometry(geometry, taper_func='taylor', Nx=None, Ny=None, **taper_kwargs)[source]#
Apply an amplitude taper based on element positions.
For non-rectangular arrays, uses radial distance from center.
- Parameters:
geometry (
ArrayGeometry) – Array geometrytaper_func (
str) – ‘taylor’, ‘chebyshev’, ‘hamming’, ‘hanning’, ‘gaussian’, ‘cosine’Nx (
int, optional) – For rectangular arrays, specify dimensionsNy (
int, optional) – For rectangular arrays, specify dimensions**taper_kwargs – Additional arguments for the taper function
- Returns:
taper – Amplitude weights for each element
- Return type:
ndarray
Null Steering#
- phased_array.null_steering_projection(geometry, k, theta_main_deg, phi_main_deg, null_directions, initial_weights=None)[source]#
Compute weights with nulls using orthogonal projection.
Projects the desired weight vector onto the null-space of the steering vectors for the null directions.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumbertheta_main_deg (
float) – Main beam theta in degreesphi_main_deg (
float) – Main beam phi in degreesnull_directions (
listof(theta_deg,phi_deg) tuples) – Directions for placing nullsinitial_weights (
ndarray, optional) – Starting weights (default: uniform with steering)
- Returns:
weights – Complex weights with nulls placed
- Return type:
ndarray
Examples
Place nulls at specific interference directions:
>>> import numpy as np >>> import phased_array as pa >>> geom = pa.create_rectangular_array(16, 16, dx=0.5, dy=0.5) >>> k = pa.wavelength_to_k(1.0) >>> # Main beam at 30 deg, nulls at 45 and 60 degrees >>> null_dirs = [(45, 0), (60, 0)] >>> weights = pa.null_steering_projection( ... geom, k, theta_main_deg=30, phi_main_deg=0, ... null_directions=null_dirs ... ) >>> weights.shape (256,)
Verify null depth:
>>> null_depth = pa.compute_null_depth( ... weights, geom, k, theta_deg=45, phi_deg=0, ... theta_main_deg=30, phi_main_deg=0 ... ) >>> null_depth < -30 # Deep null achieved True
- phased_array.null_steering_lcmv(geometry, k, constraints, noise_covariance=None)[source]#
LCMV (Linearly Constrained Minimum Variance) beamformer.
Minimizes output power subject to linear constraints on response in specified directions.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumberconstraints (
listof(theta_deg,phi_deg,response) tuples) – Each constraint specifies (direction, desired complex response) Use response=1+0j for unity gain, response=0 for nullnoise_covariance (
ndarray, optional) – N x N noise covariance matrix (default: identity)
- Returns:
weights – Complex LCMV weights
- Return type:
ndarray
Multi-Beam Synthesis#
- phased_array.multi_beam_weights_superposition(geometry, k, beam_directions, amplitudes=None, tapers=None)[source]#
Compute weights for multiple simultaneous beams via superposition.
The weights are a weighted sum of individual steering vectors. This creates multiple beams but with reduced gain per beam.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumberbeam_directions (
listof(theta_deg,phi_deg) tuples) – Directions for each beamamplitudes (
listoffloat, optional) – Relative amplitude for each beam (default: equal)tapers (
listofndarray, optional) – Amplitude taper for each beam (default: uniform)
- Returns:
weights – Combined complex weights
- Return type:
ndarray
- phased_array.multi_beam_weights_orthogonal(geometry, k, beam_directions)[source]#
Compute separate weight vectors for orthogonal digital beamforming.
Each beam has its own weight vector, allowing digital combination with no loss per beam (requires N receive channels).
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumberbeam_directions (
listof(theta_deg,phi_deg) tuples) – Directions for each beam
- Returns:
weight_vectors – List of complex weight vectors, one per beam
- Return type:
listofndarray
- phased_array.compute_beam_isolation(weights_list, geometry, k, beam_directions)[source]#
Compute isolation between multiple beams.
- Parameters:
weights_list (
listofndarray) – Weight vector for each beamgeometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumberbeam_directions (
listof(theta_deg,phi_deg)) – Direction of each beam
- Returns:
isolation_matrix – N_beams x N_beams matrix of isolation in dB Diagonal is 0 dB, off-diagonal is negative (isolation)
- Return type:
ndarray
Special Patterns#
- phased_array.monopulse_weights(geometry, k, theta0_deg, phi0_deg, mode='sum')[source]#
Compute monopulse tracking weights.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumbertheta0_deg (
float) – Nominal beam direction thetaphi0_deg (
float) – Nominal beam direction phimode (
str) – ‘sum’ - sum pattern (normal beam) ‘delta_az’ - azimuth difference pattern ‘delta_el’ - elevation difference pattern
- Returns:
weights – Complex weights for the specified mode
- Return type:
ndarray
Beam Spoiling#
Functions for beam broadening using quadratic phase distributions, commonly used in search mode to cover larger areas.
- phased_array.quadratic_phase_spoil(geometry, k, theta0_deg, phi0_deg, spoil_factor, axis='both')[source]#
Compute beam spoiling weights using quadratic phase distribution.
Quadratic phase across the aperture broadens the beam by introducing a defocusing effect, similar to moving away from the focal plane.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumber (2*pi/wavelength)theta0_deg (
float) – Steering direction theta in degreesphi0_deg (
float) – Steering direction phi in degreesspoil_factor (
float) – Spoiling factor controlling beam broadening. Higher values = broader beam. Typical range: 0.5 to 5.0axis (
str) – ‘both’ - spoil in both x and y ‘x’ - spoil only in x direction ‘y’ - spoil only in y direction
- Returns:
weights – Complex weights with steering and quadratic phase spoiling
- Return type:
ndarray
Examples
Create a spoiled beam for search mode:
>>> import numpy as np >>> import phased_array as pa >>> geom = pa.create_rectangular_array(16, 16, dx=0.5, dy=0.5) >>> k = pa.wavelength_to_k(1.0) >>> weights = pa.quadratic_phase_spoil( ... geom, k, theta0_deg=0, phi0_deg=0, spoil_factor=2.0 ... ) >>> weights.shape (256,)
Compute expected beamwidth:
>>> bw_unspoiled = 6.0 # degrees (approximate for 16x16 at lambda/2) >>> bw_spoiled = pa.spoiled_beamwidth(bw_unspoiled, spoil_factor=2.0) >>> bw_spoiled > bw_unspoiled True
Notes
- The quadratic phase distribution is:
phi_quad = spoil_factor * (x^2 + y^2) / aperture^2
This creates a beam that is approximately sqrt(1 + spoil_factor^2) times wider than the unspoiled beam.
- phased_array.compute_spoil_factor(geometry, desired_beamwidth_deg, unspoiled_beamwidth_deg)[source]#
Compute the spoil factor needed to achieve a desired beamwidth.
- Parameters:
geometry (
ArrayGeometry) – Array geometry (used to validate reasonableness)desired_beamwidth_deg (
float) – Target beamwidth in degreesunspoiled_beamwidth_deg (
float) – Natural (unspoiled) beamwidth in degrees
- Returns:
spoil_factor – Spoil factor to use with quadratic_phase_spoil
- Return type:
Examples
Double the beamwidth:
>>> import phased_array as pa >>> geom = pa.create_rectangular_array(16, 16, dx=0.5, dy=0.5) >>> sf = pa.compute_spoil_factor(geom, desired_beamwidth_deg=12.0, ... unspoiled_beamwidth_deg=6.0) >>> sf > 0 True
Notes
- The relationship between spoil factor and beamwidth broadening is:
BW_spoiled / BW_unspoiled ~ sqrt(1 + spoil_factor^2)
This is an approximation that works well for moderate spoiling.
- phased_array.spoiled_beam_gain(n_elements, element_gain_dBi, spoil_factor, taper_efficiency=1.0)[source]#
Estimate gain of a spoiled beam.
Beam spoiling reduces peak gain because power is spread over a wider solid angle.
- Parameters:
- Returns:
gain_dBi – Estimated peak gain in dBi
- Return type:
Examples
>>> import phased_array as pa >>> gain_unspoiled = 10 * np.log10(256) + 5 # 256 elements, 5 dBi each >>> gain_spoiled = pa.spoiled_beam_gain(256, 5.0, spoil_factor=2.0) >>> gain_spoiled < gain_unspoiled # Spoiling reduces gain True
Notes
- Gain loss due to spoiling is approximately:
Loss_dB ~ 10 * log10(1 + spoil_factor^2)
This corresponds to the beam area increase.
- phased_array.spoiled_beamwidth(unspoiled_beamwidth_deg, spoil_factor)[source]#
Estimate the beamwidth of a spoiled beam.
- Parameters:
- Returns:
beamwidth_deg – Estimated beamwidth of spoiled beam in degrees
- Return type:
Examples
>>> import phased_array as pa >>> bw = pa.spoiled_beamwidth(6.0, spoil_factor=2.0) >>> round(bw, 1) 13.4
Notes
- The beamwidth broadening factor is approximately:
BW_spoiled / BW_unspoiled = sqrt(1 + spoil_factor^2)
Adaptive Beamforming (SMI/GSC)#
Adaptive beamforming techniques for interference rejection using Sample Matrix Inversion (SMI) and Generalized Sidelobe Canceller (GSC).
- phased_array.adaptive_weights_smi(geometry, k, theta_desired_deg, phi_desired_deg, interference_data, diagonal_loading=0.0)[source]#
Compute adaptive weights using Sample Matrix Inversion (SMI).
SMI is a direct computation of the Minimum Variance Distortionless Response (MVDR) beamformer weights from sample data.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumbertheta_desired_deg (
float) – Desired signal direction theta in degreesphi_desired_deg (
float) – Desired signal direction phi in degreesinterference_data (
ndarray) – Interference-plus-noise data matrix (n_snapshots x n_elements) Each row is a snapshot of received signalsdiagonal_loading (
float) – Diagonal loading factor for robustness. Adds diagonal_loading * I to the covariance matrix.
- Returns:
weights – Adaptive complex weights
- Return type:
ndarray
Examples
>>> import numpy as np >>> import phased_array as pa >>> geom = pa.create_rectangular_array(8, 8, dx=0.5, dy=0.5) >>> k = pa.wavelength_to_k(1.0) >>> # Simulate interference data (normally from receiver) >>> n_snapshots = 100 >>> interference = np.random.randn(n_snapshots, geom.n_elements) + \ ... 1j * np.random.randn(n_snapshots, geom.n_elements) >>> weights = pa.adaptive_weights_smi( ... geom, k, theta_desired_deg=0, phi_desired_deg=0, ... interference_data=interference, diagonal_loading=0.01 ... ) >>> weights.shape (64,)
Notes
- The SMI weight vector is:
w = R^(-1) @ s / (s^H @ R^(-1) @ s)
where R is the sample covariance matrix and s is the steering vector. Diagonal loading improves robustness when snapshots are limited.
- phased_array.adaptive_weights_gsc(geometry, k, theta_desired_deg, phi_desired_deg, interference_data, n_blocking_vectors=None, mu=0.01)[source]#
Compute adaptive weights using Generalized Sidelobe Canceller (GSC).
GSC provides a constrained adaptive filter structure that maintains distortionless response in the look direction while minimizing interference and noise.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumbertheta_desired_deg (
float) – Desired signal direction theta in degreesphi_desired_deg (
float) – Desired signal direction phi in degreesinterference_data (
ndarray) – Interference-plus-noise data (n_snapshots x n_elements)n_blocking_vectors (
int, optional) – Number of blocking matrix columns. Default: n_elements - 1mu (
float) – Adaptation step size for LMS algorithm
- Returns:
weights (
ndarray) – Adaptive weights (n_elements,)blocking_matrix (
ndarray) – Blocking matrix used (n_elements x n_blocking_vectors)
- Return type:
Examples
>>> import numpy as np >>> import phased_array as pa >>> geom = pa.create_rectangular_array(8, 8, dx=0.5, dy=0.5) >>> k = pa.wavelength_to_k(1.0) >>> interference = np.random.randn(50, geom.n_elements) + \ ... 1j * np.random.randn(50, geom.n_elements) >>> weights, B = pa.adaptive_weights_gsc( ... geom, k, theta_desired_deg=0, phi_desired_deg=0, ... interference_data=interference ... ) >>> weights.shape (64,)
Notes
- The GSC structure is:
w = w_q - B @ w_a
- where:
w_q is the quiescent (non-adaptive) steering weight
B is a blocking matrix orthogonal to the steering vector
w_a are adaptive weights that minimize output power
The blocking matrix satisfies: s^H @ B = 0 (nulls the look direction)
- phased_array.compute_sinr_improvement(weights_before, weights_after, geometry, k, signal_direction, interference_directions, signal_power, interference_powers, noise_power)[source]#
Compute SINR improvement from adaptive beamforming.
- Parameters:
weights_before (
ndarray) – Weights before adaptationweights_after (
ndarray) – Weights after adaptationgeometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumbersignal_direction (
tuple) – (theta_deg, phi_deg) of desired signalinterference_directions (
list) – List of (theta_deg, phi_deg) for each interferersignal_power (
float) – Signal power (linear)interference_powers (
list) – Power of each interferer (linear)noise_power (
float) – Thermal noise power (linear)
- Returns:
- Return type:
Examples
>>> import numpy as np >>> import phased_array as pa >>> geom = pa.create_rectangular_array(8, 8, dx=0.5, dy=0.5) >>> k = pa.wavelength_to_k(1.0) >>> w_q = pa.steering_vector(k, geom.x, geom.y, 0, 0) >>> # Create adapted weights that null an interferer in a sidelobe >>> w_adapted = pa.null_steering_projection( ... geom, k, theta_main_deg=0, phi_main_deg=0, ... null_directions=[(20, 0)] ... ) >>> sinr_b, sinr_a, imp = pa.compute_sinr_improvement( ... w_q, w_adapted, geom, k, ... signal_direction=(0, 0), ... interference_directions=[(20, 0)], ... signal_power=1.0, interference_powers=[10.0], noise_power=0.1 ... ) >>> imp > 0 # Should show improvement True
- phased_array.plot_adapted_pattern(geometry, k, weights_quiescent, weights_adapted, interference_directions, title='Adapted Pattern Comparison', phi_cut_deg=0.0, n_points=361)[source]#
Plot comparison of quiescent and adapted antenna patterns.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumberweights_quiescent (
ndarray) – Non-adaptive (quiescent) weightsweights_adapted (
ndarray) – Adaptive weightsinterference_directions (
list) – List of (theta_deg, phi_deg) tuples for interfererstitle (
str) – Plot titlephi_cut_deg (
float) – Phi angle for the pattern cut in degreesn_points (
int) – Number of points in pattern
- Returns:
ax – The matplotlib axes object
- Return type:
Examples
>>> import numpy as np >>> import phased_array as pa >>> geom = pa.create_rectangular_array(16, 16, dx=0.5, dy=0.5) >>> k = pa.wavelength_to_k(1.0) >>> w_q = pa.steering_vector(k, geom.x, geom.y, 0, 0) >>> w_a = pa.null_steering_projection( ... geom, k, theta_main_deg=0, phi_main_deg=0, ... null_directions=[(25, 0)] ... ) >>> ax = pa.plot_adapted_pattern( ... geom, k, w_q, w_a, ... interference_directions=[(25, 0)], ... title="Null at 25 degrees" ... )