feat: 完成阵列抽象类, 常见阵列初始化函数开发
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
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
|
||||
)
|
||||
Reference in New Issue
Block a user