Core Module#

The core module provides fundamental functions for computing phased array radiation patterns, including steering vectors, array factors, and pattern cuts.

Steering and Array Factor#

phased_array.steering_vector(k, x, y, theta0_deg, phi0_deg, z=None)[source]#

Compute steering vector (phase weights) for beam pointing.

Parameters:
  • k (float) – Wavenumber (2*pi/wavelength) in rad/m

  • x (ndarray) – Element x-positions in meters (flattened)

  • y (ndarray) – Element y-positions in meters (flattened)

  • theta0_deg (float) – Desired beam steering angle theta in degrees (from z-axis)

  • phi0_deg (float) – Desired beam steering angle phi in degrees (azimuth)

  • z (ndarray, optional) – Element z-positions in meters (for 3D arrays)

Returns:

weights – Complex steering weights (unit magnitude, appropriate phases)

Return type:

ndarray

Examples

Create steering weights for a 4x4 array pointing at 30 degrees:

>>> import numpy as np
>>> import phased_array as pa
>>> geom = pa.create_rectangular_array(4, 4, 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.shape
(16,)
>>> np.abs(weights[0])  # All weights have unit magnitude
1.0

Steer to azimuth with 3D array positions:

>>> z = np.zeros(16)  # Planar array
>>> weights_3d = pa.steering_vector(k, geom.x, geom.y, 20, 45, z=z)
phased_array.array_factor_vectorized(theta, phi, x, y, weights, k, z=None)[source]#

Compute array factor using vectorized NumPy operations.

This is 50-100x faster than nested loop implementations for typical grid sizes (181x181 angular points).

Parameters:
  • theta (ndarray) – Polar angles in radians, shape (n_theta, n_phi) or (n_points,)

  • phi (ndarray) – Azimuthal angles in radians, same shape as theta

  • x (ndarray) – Element x-positions in meters, shape (n_elements,)

  • y (ndarray) – Element y-positions in meters, shape (n_elements,)

  • weights (ndarray) – Complex element weights, shape (n_elements,)

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

  • z (ndarray, optional) – Element z-positions in meters, shape (n_elements,)

Returns:

AF – Complex array factor, same shape as theta/phi

Return type:

ndarray

Examples

Compute array factor for a 4x4 array at broadside:

>>> import numpy as np
>>> import phased_array as pa
>>> geom = pa.create_rectangular_array(4, 4, dx=0.5, dy=0.5)
>>> k = pa.wavelength_to_k(1.0)
>>> weights = np.ones(16)  # Uniform weights
>>> theta = np.array([[0.0]])  # Broadside
>>> phi = np.array([[0.0]])
>>> AF = pa.array_factor_vectorized(theta, phi, geom.x, geom.y, weights, k)
>>> np.abs(AF[0, 0])  # Peak at broadside equals number of elements
16.0

Compute over a grid of angles:

>>> theta_grid, phi_grid = np.meshgrid(
...     np.linspace(0, np.pi/2, 91),
...     np.linspace(0, 2*np.pi, 181),
...     indexing='ij'
... )
>>> AF_grid = pa.array_factor_vectorized(
...     theta_grid, phi_grid, geom.x, geom.y, weights, k
... )
>>> AF_grid.shape
(91, 181)
phased_array.array_factor_uv(u, v, x, y, weights, k)[source]#

Compute array factor directly in UV-space.

Parameters:
  • u (ndarray) – Direction cosine u, shape (n_u, n_v) or (n_points,)

  • v (ndarray) – Direction cosine v, same shape as u

  • x (ndarray) – Element x-positions in meters

  • y (ndarray) – Element y-positions in meters

  • weights (ndarray) – Complex element weights

  • k (float) – Wavenumber in rad/m

Returns:

AF – Complex array factor, same shape as u/v

Return type:

ndarray

phased_array.array_factor_fft(weights_2d, dx, dy, n_u=512, n_v=512, wavelength=1.0)[source]#

Compute array factor using 2D FFT for uniform rectangular arrays.

This is O(N log N) instead of O(N * M) and is fastest for large uniform rectangular arrays.

Parameters:
  • weights_2d (ndarray) – Complex weights on 2D grid, shape (Nx, Ny)

  • dx (float) – Element spacing in x (in wavelengths)

  • dy (float) – Element spacing in y (in wavelengths)

  • n_u (int) – Number of output points in u direction

  • n_v (int) – Number of output points in v direction

  • wavelength (float) – Wavelength (for normalizing spacing, default=1 means dx, dy in wavelengths)

Returns:

  • u (ndarray) – Direction cosine u values, shape (n_u,)

  • v (ndarray) – Direction cosine v values, shape (n_v,)

  • AF (ndarray) – Complex array factor, shape (n_u, n_v)

Return type:

Tuple[ndarray, ndarray, ndarray]

Element Patterns#

phased_array.element_pattern(theta, phi, cos_exp_theta=1.0, cos_exp_phi=1.0, max_gain_dBi=0.0)[source]#

Compute element pattern using raised cosine model.

Parameters:
  • theta (ndarray) – Polar angle in radians

  • phi (ndarray) – Azimuthal angle in radians (not used; the model is phi-symmetric)

  • cos_exp_theta (float) – Cosine exponent for theta dependence (1.0 = simple cosine)

  • cos_exp_phi (float) – Deprecated and unused. The basic model has no phi dependence; for polarized/phi-dependent element models see the v1.4 vector pattern API.

  • max_gain_dBi (float) – Peak element gain in dBi

Returns:

pattern – Element pattern (linear scale, same shape as theta)

Return type:

ndarray

phased_array.element_pattern_cosine_tapered(theta, phi, theta_3dB_deg=65.0, max_gain_dBi=5.0)[source]#

Compute element pattern with specified 3dB beamwidth.

The cosine exponent is computed to achieve the desired beamwidth.

Parameters:
  • theta (ndarray) – Polar angle in radians

  • phi (ndarray) – Azimuthal angle in radians

  • theta_3dB_deg (float) – Half-power beamwidth in degrees

  • max_gain_dBi (float) – Peak element gain in dBi

Returns:

pattern – Element pattern (linear scale)

Return type:

ndarray

Pattern Computation#

phased_array.compute_full_pattern(x, y, weights, k, n_theta=181, n_phi=361, theta_range=(0, 1.5707963267948966), phi_range=(0, 6.283185307179586), element_pattern_func=None, z=None, **element_kwargs)[source]#

Compute full 2D radiation pattern.

Parameters:
  • x (ndarray) – Element positions in meters

  • y (ndarray) – Element positions in meters

  • weights (ndarray) – Complex element weights

  • k (float) – Wavenumber in rad/m

  • n_theta (int) – Number of angular points

  • n_phi (int) – Number of angular points

  • theta_range (tuple) – Angular ranges in radians

  • phi_range (tuple) – Angular ranges in radians

  • element_pattern_func (callable, optional) – Element pattern function

  • z (ndarray, optional) – Element z-positions

  • **element_kwargs – Arguments for element pattern

Returns:

  • theta (ndarray) – Theta values in radians, shape (n_theta,)

  • phi (ndarray) – Phi values in radians, shape (n_phi,)

  • pattern_dB (ndarray) – Pattern in dB, shape (n_theta, n_phi)

Return type:

Tuple[ndarray, ndarray, ndarray]

Examples

Compute a full pattern for a 16x16 array steered to 30 degrees:

>>> 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.steering_vector(k, geom.x, geom.y, theta0_deg=30, phi0_deg=0)
>>> theta, phi, pattern_dB = pa.compute_full_pattern(
...     geom.x, geom.y, weights, k, n_theta=91, n_phi=181
... )
>>> theta.shape, phi.shape, pattern_dB.shape
((91,), (181,), (91, 181))
>>> pattern_dB.max()  # Normalized to 0 dB peak
0.0

Include element pattern (cosine model):

>>> theta, phi, pattern_dB = pa.compute_full_pattern(
...     geom.x, geom.y, weights, k,
...     element_pattern_func=pa.element_pattern,
...     cos_exp_theta=1.3
... )
phased_array.compute_pattern_cuts(x, y, weights, k, theta0_deg=0.0, phi0_deg=0.0, n_points=361, theta_range_deg=(-90, 90), element_pattern_func=None, **element_kwargs)[source]#

Compute principal plane pattern cuts (E-plane and H-plane).

Parameters:
  • x (ndarray) – Element positions in meters

  • y (ndarray) – Element positions in meters

  • weights (ndarray) – Complex element weights

  • k (float) – Wavenumber in rad/m

  • theta0_deg (float) – Beam steering theta angle

  • phi0_deg (float) – Beam steering phi angle

  • n_points (int) – Number of points in each cut

  • theta_range_deg (tuple) – Angular range in degrees

  • element_pattern_func (callable, optional) – Element pattern function

  • **element_kwargs – Arguments for element pattern

Returns:

  • theta_deg (ndarray) – Angle values in degrees

  • E_plane_dB (ndarray) – E-plane pattern in dB (phi = phi0)

  • H_plane_dB (ndarray) – H-plane pattern in dB (phi = phi0 + 90)

Return type:

Tuple[ndarray, ndarray, ndarray]

phased_array.total_pattern(theta, phi, x, y, weights, k, element_pattern_func=None, z=None, **element_kwargs)[source]#

Compute total radiation pattern (element pattern * array factor).

Parameters:
  • theta (ndarray) – Polar angles in radians

  • phi (ndarray) – Azimuthal angles in radians

  • x (ndarray) – Element x-positions in meters

  • y (ndarray) – Element y-positions in meters

  • weights (ndarray) – Complex element weights

  • k (float) – Wavenumber in rad/m

  • element_pattern_func (callable, optional) – Function to compute element pattern. If None, uses isotropic elements. Should have signature: func(theta, phi, **kwargs) -> ndarray

  • z (ndarray, optional) – Element z-positions for 3D arrays

  • element_kwargs (dict) – Additional keyword arguments passed to element_pattern_func

Returns:

pattern – Total radiation pattern (complex)

Return type:

ndarray

Pattern Analysis#

phased_array.compute_directivity(theta, phi, pattern)[source]#

Compute peak directivity by integrating a pattern over solid angle.

Power is treated as constant over each theta cell. Cell boundaries are the midpoints of adjacent theta samples, clipped at the first and last samples. The weight of row i is

w_i = cos(theta_edge_i) - cos(theta_edge_{i+1})

so that sum(w_i) * (phi span) reproduces the sampled solid angle within floating-point error. Azimuth is integrated with the trapezoidal rule. Midpoint boundaries keep neighboring cells contiguous even when single-precision coordinates have small rounding differences.

The spherical Jacobian sin(theta) correctly vanishes at the poles, which individually have zero solid angle. Numerical error arises from approximating power over finite angular regions. Cell weighting can reduce that error for pole-peaked patterns and exactly integrates an isotropic pattern on a full sphere, but does not eliminate quadrature bias generally. For smooth patterns that vanish at the poles, the previous sin(theta) trapezoidal rule can be more accurate. Both rules are generally second order, with higher-order convergence possible for special patterns. Refine the angular grid to check convergence.

Parameters:
  • theta (ndarray) – 2D theta grid in radians, shape (n_theta, n_phi). Must be uniformly spaced, strictly increasing along axis 0, constant along axis 1, and contained in [0, pi] - the theta_grid output of create_theta_phi_grid().

  • phi (ndarray) – 2D phi grid in radians, same shape. Must be uniformly spaced, strictly increasing along axis 1, constant along axis 0, and span at most 2*pi.

  • pattern (ndarray) – Complex or magnitude (amplitude) pattern on the same grid. Power is taken as |pattern|**2, so pass amplitude - not power, and not dB.

Returns:

directivity – Peak directivity in linear scale (not dB)

Return type:

float

Raises:

ValueError – If theta, phi and pattern are not 2D arrays of matching shape, hold non-finite values, have fewer than two samples on an axis, are not separable in the indexing='ij' sense, are not strictly increasing with uniform spacing, sample theta outside [0, pi], or span more than 2*pi in phi

Notes

The grid need not cover the full sphere. A partial grid integrates only the solid angle it samples, which is equivalent to assuming the pattern radiates no power outside that region. A hemisphere grid (theta_range=(0, np.pi/2)) therefore gives the expected answer for a ground-plane backed array.

compute_full_pattern() output cannot be passed here directly: it returns 1D theta and phi axes and a normalized dB pattern. Build the grid with create_theta_phi_grid() and evaluate amplitude on it with total_pattern() or array_factor_vectorized() instead. A dB-valued pattern is not detectable here and integrates to a meaningless result.

Examples

An isotropic pattern has unit directivity:

>>> import numpy as np
>>> import phased_array as pa
>>> _, _, theta, phi = pa.create_theta_phi_grid()
>>> d = pa.compute_directivity(theta, phi, np.ones_like(theta))
>>> f"{d:.9f}"
'1.000000000'

A short dipole, amplitude sin(theta), has directivity 3/2:

>>> d = pa.compute_directivity(theta, phi, np.sin(theta))
>>> f"{d:.4f}"
'1.5000'
phased_array.compute_half_power_beamwidth(angles_deg, pattern_dB)[source]#

Compute half-power beamwidth from a 1D pattern cut.

Parameters:
  • angles_deg (ndarray) – Angle values in degrees

  • pattern_dB (ndarray) – Normalized pattern in dB (0 dB at peak)

Returns:

hpbw – Half-power beamwidth in degrees

Return type:

float