Impairments Module#
The impairments module provides functions to model real-world effects that degrade phased array performance, including mutual coupling, phase quantization, element failures, and scan blindness.
Mutual Coupling#
- phased_array.mutual_coupling_matrix_theoretical(geometry, k, coupling_model='sinc', coupling_coeff=0.3)[source]#
Compute theoretical mutual coupling matrix.
- Parameters:
geometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumber (2*pi/wavelength)coupling_model (
str) – ‘sinc’ - sinc function model (good for dipoles) ‘exponential’ - exponential decay modelcoupling_coeff (
float) – Coupling coefficient (typical: 0.1-0.5)
- Returns:
C – N x N complex mutual coupling matrix
- Return type:
ndarray
- phased_array.mutual_coupling_matrix_measured(s_parameters, formulation='impedance')[source]#
Convert measured S-parameters to coupling matrix.
The coupling matrix relates actual element currents to excitation voltages: I = C^(-1) @ V
- Parameters:
s_parameters (
ndarray) – N x N S-parameter matrix (complex)formulation (
str) – ‘impedance’ (default): C = (I + S)(I - S)^(-1), the normalized impedance matrix. ‘voltage’: C = I + S, the total-voltage coupling used before v1.3.2.
- Returns:
C – Mutual coupling matrix
- Return type:
ndarray
Notes
Before v1.3.2 this function returned I + S while documenting the impedance formulation; pass
formulation='voltage'to reproduce the old behavior.
- phased_array.apply_mutual_coupling(weights, C, mode='transmit')[source]#
Apply mutual coupling to element weights.
- Parameters:
weights (
ndarray) – Ideal element weights (N,)C (
ndarray) – Mutual coupling matrix (N x N)mode (
str) – ‘transmit’ - coupling affects radiated field ‘receive’ - coupling affects received signal ‘compensate’ - pre-distort to compensate coupling
- Returns:
effective_weights – Weights after coupling effects
- Return type:
ndarray
- phased_array.active_element_pattern(theta, phi, geometry, element_idx, C, k, isolated_element_pattern=None)[source]#
Compute active element pattern including mutual coupling.
The active element pattern is the pattern of a single element when all other elements are terminated in matched loads.
- Parameters:
theta (
ndarray) – Observation theta anglesphi (
ndarray) – Observation phi anglesgeometry (
ArrayGeometry) – Array geometryelement_idx (
int) – Index of the element to compute pattern forC (
ndarray) – Mutual coupling matrixk (
float) – Wavenumberisolated_element_pattern (
callable, optional) – Pattern function for isolated element
- Returns:
pattern – Active element pattern (complex)
- Return type:
ndarray
Phase Quantization#
- phased_array.quantize_phase(weights, n_bits)[source]#
Quantize phase shifter settings to discrete levels.
- Parameters:
weights (
ndarray) – Complex weights (phase will be quantized)n_bits (
int) – Number of bits for phase quantization (e.g., 3 bits = 8 levels)
- Returns:
quantized_weights – Weights with quantized phases
- Return type:
ndarray
Examples
Quantize to 3-bit phase shifters (8 levels, 45 deg steps):
>>> 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) >>> weights = pa.steering_vector(k, geom.x, geom.y, theta0_deg=15, phi0_deg=0) >>> weights_q = pa.quantize_phase(weights, n_bits=3) >>> weights_q.shape (64,)
Check phase quantization levels:
>>> phases_deg = np.rad2deg(np.angle(weights_q)) >>> np.unique(np.round(phases_deg / 45) * 45).size <= 8 True
Compare effect of different bit depths:
>>> rms_3bit = pa.quantization_rms_error(3) # ~13 degrees >>> rms_6bit = pa.quantization_rms_error(6) # ~1.6 degrees >>> rms_3bit > rms_6bit True
- phased_array.quantization_rms_error(n_bits)[source]#
Compute theoretical RMS phase error for quantization.
- phased_array.quantization_sidelobe_increase(n_bits)[source]#
Estimate sidelobe level increase due to phase quantization.
- phased_array.analyze_quantization_effect(weights, geometry, k, n_bits, theta_range=(0, 1.5707963267948966), n_points=361)[source]#
Analyze effect of phase quantization on the pattern.
- Parameters:
weights (
ndarray) – Ideal complex weightsgeometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumbern_bits (
int) – Quantization bitstheta_range (
tuple) – Range for pattern computationn_points (
int) – Number of angle points
- Returns:
results – ‘theta_deg’: angle array ‘pattern_ideal_dB’: ideal pattern ‘pattern_quantized_dB’: quantized pattern ‘difference_dB’: pattern difference
- Return type:
Element Failures#
- phased_array.simulate_element_failures(weights, failure_rate, mode='off', seed=None)[source]#
Simulate random element failures.
- Parameters:
weights (
ndarray) – Nominal element weightsfailure_rate (
float) – Probability of failure per element (0 to 1)mode (
str) – ‘off’ - failed elements have zero output ‘stuck’ - failed elements stuck at nominal magnitude, random phase ‘full’ - failed elements at full power, random phaseseed (
int, optional) – Random seed
- Returns:
degraded_weights (
ndarray) – Weights with failures appliedfailure_mask (
ndarray) – Boolean array, True for failed elements
- Return type:
Examples
Simulate 5% element failure rate:
>>> 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=0, phi0_deg=0) >>> degraded, mask = pa.simulate_element_failures( ... weights, failure_rate=0.05, mode='off', seed=42 ... ) >>> n_failed = np.sum(mask) >>> degraded.shape (256,)
Compare failure modes:
>>> # 'off' mode: failed elements produce no output >>> w_off, m_off = pa.simulate_element_failures(weights, 0.1, mode='off', seed=1) >>> np.all(w_off[m_off] == 0) True
Analyze graceful degradation:
>>> results = pa.analyze_graceful_degradation( ... weights, geom, k, failure_rates=[0.0, 0.05, 0.1], n_trials=5 ... )
- phased_array.analyze_graceful_degradation(weights, geometry, k, failure_rates, n_trials=100, mode='off')[source]#
Monte Carlo analysis of graceful degradation vs failure rate.
- Parameters:
weights (
ndarray) – Nominal weightsgeometry (
ArrayGeometry) – Array geometryk (
float) – Wavenumberfailure_rates (
list) – Failure rates to testn_trials (
int) – Number of Monte Carlo trials per ratemode (
str) – Failure mode
- Returns:
results – ‘failure_rates’: input rates ‘gain_loss_mean_dB’: mean gain loss ‘gain_loss_std_dB’: std of gain loss ‘sidelobe_increase_mean_dB’: mean sidelobe increase
- Return type:
Scan Blindness#
- phased_array.surface_wave_scan_angle(dx, dy, substrate_er=4.0, substrate_h=0.1)[source]#
Estimate scan blindness angles due to surface wave excitation.
- Parameters:
- Returns:
- Return type:
- phased_array.scan_blindness_model(theta, phi, theta_blind, phi_blind=None, null_width_deg=5.0, null_depth_dB=-30.0)[source]#
Model scan blindness as a Gaussian null at the blind angle.
- Parameters:
theta (
ndarray) – Observation theta angles (radians)phi (
ndarray) – Observation phi angles (radians)theta_blind (
float) – Blind angle theta (degrees)phi_blind (
float, optional) – Blind angle phi (degrees). If None, blindness is phi-independentnull_width_deg (
float) – Width of the null (degrees, 1-sigma)null_depth_dB (
float) – Depth of null in dB (negative)
- Returns:
factor – Multiplicative factor (0 to 1)
- Return type:
ndarray
- phased_array.apply_scan_blindness(pattern, theta, phi, theta_blind_list, phi_blind_list=None, null_width_deg=5.0, null_depth_dB=-30.0)[source]#
Apply scan blindness model to a computed pattern.
- Parameters:
pattern (
ndarray) – Complex or magnitude patterntheta (
ndarray) – Theta angles (radians)phi (
ndarray) – Phi angles (radians)theta_blind_list (
list) – List of blind angles (degrees)phi_blind_list (
list, optional) – List of blind phi anglesnull_width_deg (
float) – Null widthnull_depth_dB (
float) – Null depth
- Returns:
modified_pattern – Pattern with scan blindness applied
- Return type:
ndarray
- phased_array.compute_scan_loss(geometry, weights, k, theta_scan_deg, phi_scan_deg, element_pattern_func=None)[source]#
Compute scan loss (reduction in peak gain at scan angle).
- Parameters:
geometry (
ArrayGeometry) – Array geometryweights (
ndarray) – Element weightsk (
float) – Wavenumbertheta_scan_deg (
float) – Scan angle thetaphi_scan_deg (
float) – Scan angle phielement_pattern_func (
callable, optional) – Element pattern function
- Returns:
scan_loss_dB – Reduction in gain relative to broadside (negative or zero)
- Return type:
Active Impedance and VSWR#
Functions for computing active impedance and VSWR, which account for mutual coupling effects when the array is steered to different scan angles.
- phased_array.active_reflection_coefficient(C, weights, element_idx)[source]#
Compute active reflection coefficient for an element.
The active reflection coefficient accounts for mutual coupling from all other elements when the array is excited with given weights.
- Parameters:
C (
ndarray) – Mutual coupling matrix (N x N)weights (
ndarray) – Complex excitation weights (N,)element_idx (
int) – Index of the element to compute reflection for
- Returns:
gamma – Active reflection coefficient
- Return type:
Examples
>>> 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) >>> C = pa.mutual_coupling_matrix_theoretical(geom, k, coupling_coeff=0.2) >>> weights = pa.steering_vector(k, geom.x, geom.y, 0, 0) >>> gamma = pa.active_reflection_coefficient(C, weights, element_idx=0) >>> np.abs(gamma) < 1 # Should be reasonable True
Notes
- The active reflection coefficient is:
gamma_n = (sum_m(C_nm * w_m) / w_n) - 1
This differs from the isolated reflection coefficient because power couples from neighboring elements.
- phased_array.active_impedance(C, weights, element_idx, Z0=50.0)[source]#
Compute active impedance of an element.
- Parameters:
- Returns:
Z_active – Active impedance in ohms
- Return type:
Examples
>>> 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) >>> C = pa.mutual_coupling_matrix_theoretical(geom, k, coupling_coeff=0.2) >>> weights = pa.steering_vector(k, geom.x, geom.y, 0, 0) >>> Z = pa.active_impedance(C, weights, element_idx=0) >>> np.real(Z) > 0 # Should have positive resistance True
Notes
- Active impedance is computed from the active reflection coefficient:
Z_active = Z0 * (1 + gamma) / (1 - gamma)
This is the impedance seen looking into the element port when all elements are excited with the given weights.
- phased_array.vswr_vs_scan(geometry, C, k, theta_range=(0, 60), n_angles=61, phi_deg=0.0)[source]#
Compute VSWR for all elements versus scan angle.
- Parameters:
geometry (
ArrayGeometry) – Array geometryC (
ndarray) – Mutual coupling matrixk (
float) – Wavenumbertheta_range (
tuple) – (min, max) scan angles in degreesn_angles (
int) – Number of scan angles to computephi_deg (
float) – Phi scan plane in degrees
- Returns:
theta_deg (
ndarray) – Scan angles in degrees (n_angles,)vswr_per_element (
ndarray) – VSWR for each element at each angle (n_angles x n_elements)vswr_max (
ndarray) – Maximum VSWR across all elements at each angle (n_angles,)
- Return type:
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) >>> C = pa.mutual_coupling_matrix_theoretical(geom, k, coupling_coeff=0.2) >>> theta_deg, vswr_all, vswr_max = pa.vswr_vs_scan( ... geom, C, k, theta_range=(0, 45), n_angles=10 ... ) >>> len(vswr_max) == 10 True >>> np.all(vswr_max >= 1.0) # VSWR always >= 1 True
- phased_array.mismatch_loss(gamma)[source]#
Compute mismatch loss from reflection coefficient.
- Parameters:
gamma (
ndarray) – Complex reflection coefficient(s)- Returns:
loss_dB – Mismatch loss in dB (negative)
- Return type:
ndarray
Examples
Perfect match (gamma=0) has zero loss:
>>> import numpy as np >>> import phased_array as pa >>> loss = pa.mismatch_loss(0.0) >>> np.isclose(loss, 0.0) True
Typical 2:1 VSWR (gamma=0.333):
>>> loss = pa.mismatch_loss(0.333) >>> -0.6 < loss < -0.4 # About 0.5 dB True
Notes
- Mismatch loss is:
Loss_dB = 10 * log10(1 - |gamma|^2)
This represents the power reflected back due to mismatch.
- phased_array.active_scan_impedance_matrix(geometry, C, k, theta_deg, phi_deg, Z0=50.0)[source]#
Compute active impedance for all elements at a given scan angle.
- Parameters:
geometry (
ArrayGeometry) – Array geometryC (
ndarray) – Mutual coupling matrixk (
float) – Wavenumbertheta_deg (
float) – Scan angle theta in degreesphi_deg (
float) – Scan angle phi in degreesZ0 (
float) – Reference impedance
- Returns:
Z_active – Active impedance for each element (complex, n_elements)
- Return type:
ndarray
Examples
>>> 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) >>> C = pa.mutual_coupling_matrix_theoretical(geom, k, coupling_coeff=0.2) >>> Z = pa.active_scan_impedance_matrix(geom, C, k, theta_deg=30, phi_deg=0) >>> Z.shape (16,) >>> np.all(np.real(Z) > 0) # All should have positive resistance True
Notes
This function computes the active impedance seen by each element when the array is steered to the specified direction. Elements at different positions in the array may have different active impedances due to edge effects and the scan angle.