Array Geometries#

This guide covers the various array geometries supported by the library, from simple rectangular grids to complex conformal arrays.

Planar Arrays#

Rectangular Grid#

The most common array configuration. Elements are arranged on a regular rectangular grid with specified spacing.

import phased_array as pa

# 16x16 array with half-wavelength spacing
geom = pa.create_rectangular_array(
    Nx=16, Ny=16,
    dx=0.5, dy=0.5  # spacing in wavelengths
)
print(f"Elements: {geom.n_elements}")  # 256

# Access element positions
print(f"X range: {geom.x.min():.2f} to {geom.x.max():.2f}")
print(f"Y range: {geom.y.min():.2f} to {geom.y.max():.2f}")

Key parameters:

  • Nx, Ny: Number of elements in each dimension

  • dx, dy: Element spacing in wavelengths

  • wavelength: Physical wavelength (converts spacing to meters)

  • center: If True (default), center array at origin

Triangular Grid#

Offset triangular (hexagonal) lattice provides ~13% better packing efficiency than rectangular grids while maintaining similar grating lobe performance.

geom = pa.create_triangular_array(
    Nx=16, Ny=16,
    dx=0.5  # dy is automatically set for equilateral spacing
)
print(f"Elements: {geom.n_elements}")  # ~240 (fewer than 16x16)

The row offset is dx/2, and default dy = dx * sqrt(3)/2 for equilateral triangular cells.

Elliptical Boundary#

Arrays within elliptical (or circular) boundaries are common in radar systems.

# Circular array (a = b)
geom_circle = pa.create_elliptical_array(
    a=4.0, b=4.0,  # semi-axes in wavelengths
    dx=0.5,
    grid_type='rectangular'
)

# Elliptical with triangular grid
geom_ellipse = pa.create_elliptical_array(
    a=5.0, b=3.0,
    dx=0.5,
    grid_type='triangular'
)

Ring and Circular Arrays#

Circular Array (Single Ring)#

Elements arranged in a single ring, useful for direction finding.

geom = pa.create_circular_array(
    n_elements=16,
    radius=2.0,  # in wavelengths
    start_angle=0.0  # radians
)

Concentric Rings#

Multiple concentric rings, often used for low-sidelobe designs.

geom = pa.create_concentric_rings_array(
    n_rings=4,
    elements_per_ring=[8, 12, 16, 20],  # increasing with radius
    ring_spacing=0.5,
    include_center=True
)

Conformal Arrays#

Conformal arrays conform to curved surfaces. The key difference from planar arrays is that elements have different orientations (normal vectors).

Cylindrical Array#

Elements on a cylindrical surface, normals pointing radially outward.

geom = pa.create_cylindrical_array(
    n_azimuth=32,  # around circumference
    n_vertical=8,  # vertical rings
    radius=5.0,    # wavelengths
    height=4.0     # wavelengths
)
print(f"Is conformal: {geom.is_conformal}")  # True

Spherical Array#

Elements on a spherical cap, useful for hemispherical coverage.

geom = pa.create_spherical_array(
    n_theta=8,
    n_phi=32,
    radius=5.0,
    theta_min=0.0,       # start at pole
    theta_max=np.pi/2    # hemisphere
)

Computing Patterns for Conformal Arrays#

Conformal arrays require special pattern computation that accounts for element orientations:

import numpy as np

# Create cylindrical array
geom = pa.create_cylindrical_array(16, 4, radius=3.0, height=2.0)
k = pa.wavelength_to_k(1.0)

# Steering weights
weights = pa.steering_vector(k, geom.x, geom.y, theta0_deg=0, phi0_deg=0, z=geom.z)

# Use conformal array factor (accounts for element normals)
theta = np.linspace(0, np.pi/2, 91)
phi = np.linspace(0, 2*np.pi, 181)
theta_grid, phi_grid = np.meshgrid(theta, phi, indexing='ij')

AF = pa.array_factor_conformal(
    theta_grid, phi_grid, geom, weights, k,
    element_pattern_func=pa.element_pattern
)

Sparse/Thinned Arrays#

Sparse arrays remove elements from a full grid to reduce cost while maintaining the same aperture size. The tradeoff is higher sidelobes.

Random Thinning#

Simple random element removal:

# Start with full array
full_geom = pa.create_rectangular_array(32, 32, dx=0.5, dy=0.5)

# Keep 50% of elements randomly
sparse_geom = pa.thin_array_random(
    full_geom,
    thinning_factor=0.5,
    seed=42  # for reproducibility
)
print(f"Full: {full_geom.n_elements}, Sparse: {sparse_geom.n_elements}")

Density Taper Thinning#

Remove elements with probability based on position (more elements at center):

def taylor_density(x, y):
    # Higher probability near center
    r = np.sqrt(x**2 + y**2)
    r_max = np.sqrt(x.max()**2 + y.max()**2)
    return 1.0 - 0.7 * (r / r_max)**2

sparse_geom = pa.thin_array_density_tapered(
    full_geom,
    density_func=taylor_density,
    seed=42
)

Genetic Algorithm Optimization#

Optimize element selection to minimize an objective (e.g., peak sidelobe):

def sidelobe_objective(geom):
    # Compute peak sidelobe level
    k = pa.wavelength_to_k(1.0)
    weights = np.ones(geom.n_elements)
    theta, phi, pattern_dB = pa.compute_full_pattern(
        geom.x, geom.y, weights, k, n_theta=91, n_phi=1
    )
    # Return peak sidelobe (excluding main beam)
    main_beam_idx = np.argmax(pattern_dB[:, 0])
    sidelobes = np.concatenate([
        pattern_dB[:main_beam_idx-5, 0],
        pattern_dB[main_beam_idx+5:, 0]
    ])
    return np.max(sidelobes)

optimized_geom = pa.thin_array_genetic_algorithm(
    full_geom,
    n_target=500,  # keep 500 elements
    objective_func=sidelobe_objective,
    population_size=30,
    n_generations=50,
    seed=42
)

Subarray Architectures#

Subarrays group elements that share a common phase shifter, reducing hardware cost at the expense of beam quality.

# Create 64x64 array divided into 8x8 subarrays
architecture = pa.create_rectangular_subarrays(
    Nx_total=64, Ny_total=64,
    Nx_sub=8, Ny_sub=8,  # 8x8 elements per subarray
    dx=0.5, dy=0.5
)

print(f"Total elements: {architecture.geometry.n_elements}")  # 4096
print(f"Number of subarrays: {architecture.n_subarrays}")     # 64

# Compute subarray-level steering
k = pa.wavelength_to_k(1.0)
weights = pa.compute_subarray_weights(
    architecture, k,
    theta0_deg=20, phi0_deg=0
)

ArrayGeometry Class#

All geometry functions return an ArrayGeometry dataclass:

from dataclasses import dataclass

@dataclass
class ArrayGeometry:
    x: np.ndarray      # X positions (wavelengths or meters)
    y: np.ndarray      # Y positions
    z: np.ndarray      # Z positions (for 3D arrays)
    nx: np.ndarray     # Normal vector X components
    ny: np.ndarray     # Normal vector Y components
    nz: np.ndarray     # Normal vector Z components
    element_indices: np.ndarray  # Original indices

# Properties
geom.n_elements   # Number of elements
geom.is_planar    # True if all z values are equal
geom.is_conformal # True if element normals vary

Visualizing Array Geometries#

# 2D plot (matplotlib)
pa.plot_array_geometry(geom.x, geom.y)

# 3D interactive plot (plotly)
fig = pa.plot_array_geometry_3d_plotly(geom)
fig.show()