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.

Parameters:
  • n (int) – Number of elements

  • sidelobe_dB (float) – Desired peak sidelobe level in dB (negative)

  • nbar (int) – Number of nearly equal-level sidelobes

Returns:

taper – Amplitude taper weights (n,)

Return type:

ndarray

phased_array.chebyshev_taper_1d(n, sidelobe_dB=-30.0)[source]#

Generate 1D Dolph-Chebyshev window for equi-ripple sidelobes.

Parameters:
  • n (int) – Number of elements

  • sidelobe_dB (float) – Desired sidelobe level in dB (negative)

Returns:

taper – Amplitude taper weights

Return type:

ndarray

phased_array.hamming_taper_1d(n)[source]#

Generate 1D Hamming window.

phased_array.hanning_taper_1d(n)[source]#

Generate 1D Hanning (Hann) window.

phased_array.cosine_taper_1d(n)[source]#

Generate 1D cosine (sine) window.

phased_array.cosine_on_pedestal_taper_1d(n, pedestal=0.1)[source]#

Generate 1D cosine-on-pedestal window.

Parameters:
  • n (int) – Number of elements

  • pedestal (float) – Minimum amplitude at edges (0 to 1)

Returns:

taper – Amplitude taper

Return type:

ndarray

phased_array.gaussian_taper_1d(n, sigma=0.4)[source]#

Generate 1D Gaussian window.

Parameters:
  • n (int) – Number of elements

  • sigma (float) – Standard deviation as fraction of array (typical: 0.3-0.5)

Returns:

taper

Return type:

ndarray

Amplitude Tapers (2D)#

phased_array.taylor_taper_2d(Nx, Ny, sidelobe_dB=-30.0, nbar=4)[source]#

Generate 2D Taylor window (separable product).

Parameters:
  • Nx (int) – Number of elements in x and y

  • Ny (int) – Number of elements in x and y

  • sidelobe_dB (float) – Desired peak sidelobe level in dB

  • nbar (int) – Number of nearly equal-level sidelobes

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).

Parameters:
  • Nx (int) – Number of elements

  • Ny (int) – Number of elements

  • sidelobe_dB (float) – Desired sidelobe level in dB

Returns:

taper – 2D taper, flattened

Return type:

ndarray

phased_array.hamming_taper_2d(Nx, Ny)[source]#

Generate 2D Hamming window.

phased_array.hanning_taper_2d(Nx, Ny)[source]#

Generate 2D Hanning window.

phased_array.cosine_taper_2d(Nx, Ny)[source]#

Generate 2D cosine window.

phased_array.cosine_on_pedestal_taper_2d(Nx, Ny, pedestal=0.1)[source]#

Generate 2D cosine-on-pedestal window.

phased_array.gaussian_taper_2d(Nx, Ny, sigma=0.4)[source]#

Generate 2D Gaussian window.

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:

float

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:

float

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 geometry

  • taper_func (str) – ‘taylor’, ‘chebyshev’, ‘hamming’, ‘hanning’, ‘gaussian’, ‘cosine’

  • Nx (int, optional) – For rectangular arrays, specify dimensions

  • Ny (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 geometry

  • k (float) – Wavenumber

  • theta_main_deg (float) – Main beam theta in degrees

  • phi_main_deg (float) – Main beam phi in degrees

  • null_directions (list of (theta_deg, phi_deg) tuples) – Directions for placing nulls

  • initial_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 geometry

  • k (float) – Wavenumber

  • constraints (list of (theta_deg, phi_deg, response) tuples) – Each constraint specifies (direction, desired complex response) Use response=1+0j for unity gain, response=0 for null

  • noise_covariance (ndarray, optional) – N x N noise covariance matrix (default: identity)

Returns:

weights – Complex LCMV weights

Return type:

ndarray

phased_array.compute_null_depth(weights, geometry, k, theta_deg, phi_deg, theta_main_deg, phi_main_deg)[source]#

Compute null depth relative to main beam in dB.

Parameters:
  • weights (ndarray) – Array weights

  • geometry (ArrayGeometry) – Array geometry

  • k (float) – Wavenumber

  • theta_deg (float) – Null direction

  • phi_deg (float) – Null direction

  • theta_main_deg (float) – Main beam direction

  • phi_main_deg (float) – Main beam direction

Returns:

depth_dB – Null depth (negative, lower is deeper)

Return type:

float

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 geometry

  • k (float) – Wavenumber

  • beam_directions (list of (theta_deg, phi_deg) tuples) – Directions for each beam

  • amplitudes (list of float, optional) – Relative amplitude for each beam (default: equal)

  • tapers (list of ndarray, 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 geometry

  • k (float) – Wavenumber

  • beam_directions (list of (theta_deg, phi_deg) tuples) – Directions for each beam

Returns:

weight_vectors – List of complex weight vectors, one per beam

Return type:

list of ndarray

phased_array.compute_beam_isolation(weights_list, geometry, k, beam_directions)[source]#

Compute isolation between multiple beams.

Parameters:
  • weights_list (list of ndarray) – Weight vector for each beam

  • geometry (ArrayGeometry) – Array geometry

  • k (float) – Wavenumber

  • beam_directions (list of (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 geometry

  • k (float) – Wavenumber

  • theta0_deg (float) – Nominal beam direction theta

  • phi0_deg (float) – Nominal beam direction phi

  • mode (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 geometry

  • k (float) – Wavenumber (2*pi/wavelength)

  • theta0_deg (float) – Steering direction theta in degrees

  • phi0_deg (float) – Steering direction phi in degrees

  • spoil_factor (float) – Spoiling factor controlling beam broadening. Higher values = broader beam. Typical range: 0.5 to 5.0

  • axis (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 degrees

  • unspoiled_beamwidth_deg (float) – Natural (unspoiled) beamwidth in degrees

Returns:

spoil_factor – Spoil factor to use with quadratic_phase_spoil

Return type:

float

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:
  • n_elements (int) – Number of array elements

  • element_gain_dBi (float) – Single element gain in dBi

  • spoil_factor (float) – Spoiling factor used

  • taper_efficiency (float) – Aperture taper efficiency (0 to 1)

Returns:

gain_dBi – Estimated peak gain in dBi

Return type:

float

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:
  • unspoiled_beamwidth_deg (float) – Natural (unspoiled) beamwidth in degrees

  • spoil_factor (float) – Spoiling factor used

Returns:

beamwidth_deg – Estimated beamwidth of spoiled beam in degrees

Return type:

float

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 geometry

  • k (float) – Wavenumber

  • theta_desired_deg (float) – Desired signal direction theta in degrees

  • phi_desired_deg (float) – Desired signal direction phi in degrees

  • interference_data (ndarray) – Interference-plus-noise data matrix (n_snapshots x n_elements) Each row is a snapshot of received signals

  • diagonal_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 geometry

  • k (float) – Wavenumber

  • theta_desired_deg (float) – Desired signal direction theta in degrees

  • phi_desired_deg (float) – Desired signal direction phi in degrees

  • interference_data (ndarray) – Interference-plus-noise data (n_snapshots x n_elements)

  • n_blocking_vectors (int, optional) – Number of blocking matrix columns. Default: n_elements - 1

  • mu (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:

Tuple[ndarray, 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)
>>> 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 adaptation

  • weights_after (ndarray) – Weights after adaptation

  • geometry (ArrayGeometry) – Array geometry

  • k (float) – Wavenumber

  • signal_direction (tuple) – (theta_deg, phi_deg) of desired signal

  • interference_directions (list) – List of (theta_deg, phi_deg) for each interferer

  • signal_power (float) – Signal power (linear)

  • interference_powers (list) – Power of each interferer (linear)

  • noise_power (float) – Thermal noise power (linear)

Returns:

  • sinr_before (float) – SINR before adaptation in dB

  • sinr_after (float) – SINR after adaptation in dB

  • improvement_dB (float) – SINR improvement in dB

Return type:

Tuple[float, float, float]

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 geometry

  • k (float) – Wavenumber

  • weights_quiescent (ndarray) – Non-adaptive (quiescent) weights

  • weights_adapted (ndarray) – Adaptive weights

  • interference_directions (list) – List of (theta_deg, phi_deg) tuples for interferers

  • title (str) – Plot title

  • phi_cut_deg (float) – Phi angle for the pattern cut in degrees

  • n_points (int) – Number of points in pattern

Returns:

ax – The matplotlib axes object

Return type:

matplotlib.axes.Axes

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"
... )