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 geometry

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

  • coupling_model (str) – ‘sinc’ - sinc function model (good for dipoles) ‘exponential’ - exponential decay model

  • coupling_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 angles

  • phi (ndarray) – Observation phi angles

  • geometry (ArrayGeometry) – Array geometry

  • element_idx (int) – Index of the element to compute pattern for

  • C (ndarray) – Mutual coupling matrix

  • k (float) – Wavenumber

  • isolated_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.

Parameters:

n_bits (int) – Number of phase quantization bits

Returns:

rms_error_deg – RMS phase error in degrees

Return type:

float

phased_array.quantization_sidelobe_increase(n_bits)[source]#

Estimate sidelobe level increase due to phase quantization.

Parameters:

n_bits (int) – Number of phase quantization bits

Returns:

increase_dB – Expected sidelobe increase in dB

Return type:

float

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 weights

  • geometry (ArrayGeometry) – Array geometry

  • k (float) – Wavenumber

  • n_bits (int) – Quantization bits

  • theta_range (tuple) – Range for pattern computation

  • n_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:

dict

Element Failures#

phased_array.simulate_element_failures(weights, failure_rate, mode='off', seed=None)[source]#

Simulate random element failures.

Parameters:
  • weights (ndarray) – Nominal element weights

  • failure_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 phase

  • seed (int, optional) – Random seed

Returns:

  • degraded_weights (ndarray) – Weights with failures applied

  • failure_mask (ndarray) – Boolean array, True for failed elements

Return type:

Tuple[ndarray, ndarray]

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 weights

  • geometry (ArrayGeometry) – Array geometry

  • k (float) – Wavenumber

  • failure_rates (list) – Failure rates to test

  • n_trials (int) – Number of Monte Carlo trials per rate

  • mode (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:

dict

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:
  • dx (float) – Element spacing in x (wavelengths)

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

  • substrate_er (float) – Substrate relative permittivity

  • substrate_h (float) – Substrate height (wavelengths)

Returns:

  • theta_blind_E (float) – Blind angle in E-plane (degrees)

  • theta_blind_H (float) – Blind angle in H-plane (degrees)

Return type:

Tuple[float, float]

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-independent

  • null_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 pattern

  • theta (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 angles

  • null_width_deg (float) – Null width

  • null_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 geometry

  • weights (ndarray) – Element weights

  • k (float) – Wavenumber

  • theta_scan_deg (float) – Scan angle theta

  • phi_scan_deg (float) – Scan angle phi

  • element_pattern_func (callable, optional) – Element pattern function

Returns:

scan_loss_dB – Reduction in gain relative to broadside (negative or zero)

Return type:

float

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:

complex

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:
  • C (ndarray) – Mutual coupling matrix (N x N)

  • weights (ndarray) – Complex excitation weights (N,)

  • element_idx (int) – Index of the element

  • Z0 (float) – Reference impedance in ohms (default 50)

Returns:

Z_active – Active impedance in ohms

Return type:

complex

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 geometry

  • C (ndarray) – Mutual coupling matrix

  • k (float) – Wavenumber

  • theta_range (tuple) – (min, max) scan angles in degrees

  • n_angles (int) – Number of scan angles to compute

  • phi_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:

Tuple[ndarray, ndarray, ndarray]

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

Notes

VSWR is computed from reflection coefficient:

VSWR = (1 + |gamma|) / (1 - |gamma|)

High VSWR at certain scan angles indicates potential scan blindness or poor matching conditions.

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 geometry

  • C (ndarray) – Mutual coupling matrix

  • k (float) – Wavenumber

  • theta_deg (float) – Scan angle theta in degrees

  • phi_deg (float) – Scan angle phi in degrees

  • Z0 (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.