Files

229 lines
8.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import numpy as np
from typing import Tuple, Optional
from .base_array import *
def create_ula(
elem_num: int,
spacing: float,
position_errors: Optional[np.ndarray] = None,
gain_errors: Optional[np.ndarray] = None,
phase_errors: Optional[np.ndarray] = None
) -> Array1D:
"""
Create a Uniform Linear Array (ULA) along the x-axis with specified element count and spacing.
Args:
elem_num (int): Number of elements M (must be a positive integer).
spacing (float): Distance between adjacent elements (in meters or wavelength units, must be > 0).
position_errors (Optional[np.ndarray]): Optional perturbation offsets for each element, (M,), (2, M), or (3, M).
gain_errors (Optional[np.ndarray]): Optional gain error factors (amplitude scale), shape (M,).
phase_errors (Optional[np.ndarray]): Optional phase errors (in radians), shape (M,).
Returns:
Array1D: Initialized ULA object with 1D position array.
Raises:
ValueError: If elem_num is not a positive integer or spacing is not positive.
Notes:
- Element positions are on the x-axis only.
- `positions` is a 1D NumPy array of shape (M,), i.e., scalar x-coordinates.
"""
if not isinstance(elem_num, int) or elem_num <= 0:
raise ValueError(f"elem_num must be a positive integer, got {elem_num}.")
if not isinstance(spacing, (float, int)) or spacing <= 0:
raise ValueError(f"spacing must be a positive number, got {spacing}.")
# Uniform 1D positions: [0, spacing, ..., (M-1)*spacing]
indices = np.arange(elem_num)
positions = spacing * indices # shape: (M,)
return Array1D(
positions=positions,
type=ArrayType.ULA,
spacing=spacing,
position_errors=position_errors,
gain_errors=gain_errors,
phase_errors=phase_errors
)
def create_ura(
array_size: Tuple[int, int],
spacing: float,
position_errors: Optional[np.ndarray] = None,
gain_errors: Optional[np.ndarray] = None,
phase_errors: Optional[np.ndarray] = None
) -> Array2D:
"""
Create a Uniform Rectangular Array (URA) in the xy-plane.
The array consists of elements placed on a 2D rectangular grid, with uniform
spacing in both x and y directions. The array lies entirely in the z=0 plane.
Args:
array_size (Tuple[int, int]): Number of elements along (cols, rows), i.e., (x-axis, y-axis).
The total number of elements is M = cols * rows.
spacing (float): Distance between adjacent elements (in meters or wavelength units).
position_errors (Optional[np.ndarray]): Optional position perturbations of shape (2, M) or (3, M).
gain_errors (Optional[np.ndarray]): Optional gain errors of shape (M,).
phase_errors (Optional[np.ndarray]): Optional phase errors (in radians), shape (M,).
Returns:
Array2D: Initialized uniform rectangular array object.
Raises:
ValueError: If array dimensions are not positive integers or spacing is not positive.
Notes:
- Output positions are of shape (2, M), each column is [x; y] of an element.
- Elements are ordered row-first: for each row, elements increase left to right (x).
- The array is centered at the origin only if manually shifted after creation.
"""
num_cols, num_rows = array_size
if not isinstance(num_cols, int) or num_cols <= 0 or not isinstance(num_cols, int) or num_cols <= 0:
raise ValueError(f"array_size must be two positive integers, got ({num_cols}, {num_cols}).")
if not isinstance(spacing, (float, int)) or spacing <= 0:
raise ValueError(f"spacing must be positive, got {spacing}.")
total_elements = num_cols * num_rows
# Generate 2D grid coordinates in xy-plane
x_coords = spacing * np.arange(num_cols) # x: fast-changing axis
y_coords = spacing * np.arange(num_rows) # y: slow-changing axis
xv, yv = np.meshgrid(x_coords, y_coords) # shape (cols, rows)
# Flatten and stack into (2, M) position matrix
positions = np.zeros((2, total_elements), dtype=float)
positions[0, :] = xv.flatten() # x-axis
positions[1, :] = yv.flatten() # y-axis
return Array2D(
positions=positions,
type=ArrayType.URA,
spacing=spacing,
position_errors=position_errors,
gain_errors=gain_errors,
phase_errors=phase_errors
)
def create_uca(
elem_num: int,
radius: float,
position_errors: Optional[np.ndarray] = None,
gain_errors: Optional[np.ndarray] = None,
phase_errors: Optional[np.ndarray] = None
) -> Array2D:
"""
Create a Uniform Circular Array (UCA) centered at the origin in the xy-plane.
The array elements are evenly spaced along the circumference of a circle,
with the first element at angle 0 (x-axis), and proceeding counterclockwise.
Args:
elem_num (int): Number of elements M (must be a positive integer).
radius (float): Radius of the circular array (in meters or wavelength units, must be > 0).
position_errors (Optional[np.ndarray]): Optional position perturbations, shape (2, M) or (3, M).
gain_errors (Optional[np.ndarray]): Optional gain error factors (amplitude scale), shape (M,).
phase_errors (Optional[np.ndarray]): Optional phase errors (in radians), shape (M,).
Returns:
Array2D: Initialized array object with circular geometry.
Raises:
ValueError:
- If elem_num is not a positive integer.
- If radius is not a positive number.
Notes:
- The output positions are of shape (2, M), representing [x; y] coordinates.
- The nominal element spacing (along the arc) is returned as the 'spacing' field.
- The array lies entirely in the xy-plane (z = 0 is implied).
"""
if not isinstance(elem_num, int) or elem_num <= 0:
raise ValueError(f"elem_num ({elem_num}) must be a positive integer.")
if not isinstance(radius, (float, int)) or radius <= 0:
raise ValueError(f"radius ({radius}) must be a positive number.")
# Compute angular positions (uniform spacing)
theta = np.linspace(0, 2 * np.pi, elem_num, endpoint=False)
# Compute Cartesian coordinates in 2D
x = radius * np.cos(theta)
y = radius * np.sin(theta)
# Store as 2×N position matrix: positions[0, :] = x, positions[1, :] = y
positions = np.zeros((2, elem_num), dtype=float)
positions[0, :] = x
positions[1, :] = y
# Compute approximate arc-length spacing between adjacent elements
step = 2 * np.pi / elem_num
spacing = 2 * radius * np.sin(step / 2) # chord length ≈ arc length for small step
return Array2D(
positions=positions,
type=ArrayType.UCA,
spacing=spacing,
position_errors=position_errors,
gain_errors=gain_errors,
phase_errors=phase_errors
)
def create_cyl_array_with_uca(
elem_per_layer: int,
radius: float,
num_layers: int,
layer_spacing: float,
gain_errors: Optional[np.ndarray] = None,
phase_errors: Optional[np.ndarray] = None,
position_errors: Optional[np.ndarray] = None
) -> Array3D:
"""
Create a cylindrical array composed of stacked UCAs along the z-axis.
Args:
elem_per_layer (int): Number of elements per circular UCA layer (must be > 0).
radius (float): Radius of each UCA layer (must be > 0).
num_layers (int): Number of stacked layers along z-axis (must be > 0).
layer_spacing (float): Distance between adjacent layers along z-axis (must be > 0).
gain_errors (Optional[np.ndarray]): Optional gain errors, shape (M,).
phase_errors (Optional[np.ndarray]): Optional phase errors, shape (M,).
position_errors (Optional[np.ndarray]): Optional position errors, shape (3, M).
Returns:
Array3D: Array object with cylindrical geometry.
Raises:
ValueError: If any input is invalid (non-positive integer or float).
"""
if elem_per_layer <= 0 or num_layers <= 0:
raise ValueError("elem_per_layer and num_layers must be positive integers.")
if radius <= 0:
raise ValueError("radius must be a positive number.")
if layer_spacing <= 0:
raise ValueError("layer_spacing must be a positive number.")
total_elements = elem_per_layer * num_layers
positions = np.zeros((3, total_elements), dtype=float)
theta = np.linspace(0, 2 * np.pi, elem_per_layer, endpoint=False)
x = radius * np.cos(theta)
y = radius * np.sin(theta)
for i in range(num_layers):
z = layer_spacing * i
idx_start = i * elem_per_layer
idx_end = (i + 1) * elem_per_layer
positions[0, idx_start:idx_end] = x
positions[1, idx_start:idx_end] = y
positions[2, idx_start:idx_end] = z
return Array3D(
positions=positions,
type=ArrayType.CYLINDRICAL,
spacing=layer_spacing,
position_errors=position_errors,
gain_errors=gain_errors,
phase_errors=phase_errors
)