phased-array-modeling 1.4.0: polarized patterns for phased arrays
Versions 1.3.2 and 1.4.0 of phased-array-modeling are on PyPI. The headline is v1.4.0: the pattern engine now computes vector fields, complex E-theta and E-phi components, instead of scalar magnitudes, which makes polarization a first-class output rather than an afterthought. v1.3.2 is the other kind of release, a batch of correctness fixes that came out of turning every documented example into a test.
If the package is new to you,
the intro post covers the fundamentals: array
factors, tapering, impairments, and the trade-study layer above them. Since that post, three
releases have landed; v1.3.0 added six analysis modules (polarization math, coordinate
transforms, beam spoiling, overlapped subarrays, adaptive nulling, and active impedance), and
the documentation covers them.
The package installs as pip install phased-array-modeling and imports as phased_array.
Patterns become vectors
A scalar pattern answers one question: how much power goes in each direction. Real antennas raise a second one: in what polarization. An x-oriented dipole radiating broadside is cleanly linear, but scan away from boresight, or look in the diagonal planes, and cross-polarized energy appears. Scalar array models cannot show any of that.
The new vector_patterns module wires the v1.3.0 polarization math into the pattern engine.
An element model is now any callable that returns complex (E_theta, E_phi) components, and
four factories ship: dipole_element() (x, y, or z Hertzian dipole), ideal_patch_element()
(cos-power co-pol with zero cross-pol by construction), cos_q_polarized_element() (a cos-q
roll-off in an arbitrary Jones polarization state), and crossed_dipole_element() (a
circularly polarized turnstile; a phase difference of minus 90 degrees gives RHCP, plus 90
LHCP). The array result comes back as a VectorPattern with power, Ludwig-3 co_cross()
decomposition, axial_ratio_map(), and xpd_map() methods.
The cut-level workflow is one call:
import numpy as np
from phased_array import compute_co_cross_pattern_cuts, dipole_element
theta_deg, co_dB, cross_dB = compute_co_cross_pattern_cuts(
x, y, weights, k,
element_func=dipole_element("x"),
phi_cut_deg=45.0,
)

Generated with the code above: an 8x8 half-wavelength array of x-dipole elements steered to 30 degrees. In the principal plane the cross-polarization is below the plot floor; in the 45-degree diagonal cut it climbs to about -15 dB at wide angles, which is exactly the behavior a scalar pattern model cannot show.
For dual-polarized apertures, dual_pol_weights() scales the x- and y-port excitations to
synthesize a target Jones state, so a slant-45 or circular polarization becomes a weight
calculation instead of a hand-derived phase table.
Measured elements plug in
Analytic element models only go so far; the pattern that matters is the embedded element
pattern from a full-wave solver or an anechoic chamber. GriddedElementPattern wraps a
measured or simulated (E_theta, E_phi) grid with an interpolator and is accepted anywhere an
element function is, so the same array code runs on ideal dipoles or on your chamber data. When
only scalar magnitude data exists, GriddedElementPattern.from_scalar() combines it with a
Jones vector to build a polarized pattern. This is the bridge between element-level simulation
or measurement and array-level analysis, and it needs no change to the rest of a script.
Conformal arrays get polarization right
On a curved aperture, every element points somewhere different, and so does its polarization.
The new conformal path handles this properly: element_rotation_matrices() builds a local
frame per element from its normal (and an optional tangent, via new ArrayGeometry fields),
global_to_local_angles() maps each far-field direction into that frame, and
vector_array_factor_conformal() evaluates each element locally, rotates its field back to the
global spherical basis, and sums. Per-element embedded patterns are supported, so a cylinder
with different edge and center elements is expressible. The conformal vector path loops per
element rather than vectorizing across the aperture; it favors correctness over speed, and the
planar vector path stays fully vectorized.
One behavior change rides along: array_factor_conformal() now passes local theta and local
phi, both derived from the element’s rotation matrix, to element pattern functions. The bundled
phi-independent elements produce identical results; custom phi-dependent callables now receive
correct local angles where they previously got a mix of local theta and global phi.
v1.3.2 fixed real bugs
The fixes release deserves its own accounting, because several bugs were the kind that produce
plausible wrong answers. overlapped_subarray_weights() raised an ImportError for
non-overlapped architectures. mutual_coupling_matrix_measured() did not implement its
documented impedance formulation; it now computes C = (I + S)(I - S)^-1 as documented, with the
previous behavior available via formulation='voltage'. The az/el coordinate transforms used
inconsistent conventions and were not mutual inverses; they now round-trip exactly under the
documented convention. compute_half_power_beamwidth() snapped to grid samples and now
interpolates the -3 dB crossings. And several docstring examples called functions with wrong
arguments or asserted wrong values.
The common thread is the last item. Every module docstring now runs as a doctest in CI, and 48 new tests cover previously untested modules, which is how most of these surfaced. The package now carries 253 tests, 36 of them for the new vector engine.
Upgrade notes
- Python 3.9 or later is required as of v1.4.0 (3.8 support dropped).
cos_exp_phiis deprecated (it emits a DeprecationWarning); usecos_q_polarized_element()for a shaped, polarized element.mutual_coupling_matrix_measured()defaults to the impedance formulation; passformulation='voltage'for the old behavior.azel_to_thetaphi()andthetaphi_to_azel()follow the documented convention and round-trip exactly; code that compensated for the old inconsistency should drop the workaround.- Custom phi-dependent element callables used with
array_factor_conformal()now receive local angles.
Try it
pip install phased-array-modeling
The docs gained a polarization user guide and API pages for the new module. The Streamlit app has a new Polarization page with the element models, co/cross cuts, and boresight axial ratio, and the demo notebook in the repository walks the same material. For intuition about polarization itself, the ellipse and handedness live interactively in OpenEM Wave Lab.
phased-array-modeling is an independent project I build on my own time. The views are my own and do not represent any current or former employer.
Frequently asked questions
What does the vector pattern engine in v1.4.0 add?
Patterns are now computed as complex (E-theta, E-phi) field components instead of scalar magnitudes, so polarization becomes a first-class output: co- and cross-polarized cuts with a Ludwig-3 reference, axial ratio maps, cross-polarization discrimination (XPD) maps, and dual-polarized weight synthesis.
Can I use measured or simulated element patterns?
Yes. GriddedElementPattern wraps a measured or simulated (E-theta, E-phi) grid with an interpolator and is usable anywhere an element function is accepted. A from_scalar constructor builds a polarized pattern from a magnitude grid plus a Jones vector when only scalar data exists.
What changed for conformal arrays?
The conformal path is now polarization-correct: each element is evaluated in its own local frame and the fields are rotated back to the global spherical basis. Separately, array_factor_conformal now passes local theta and phi to element functions; bundled phi-independent elements are unchanged, but custom phi-dependent callables now receive correct local angles.
What should I check before upgrading?
v1.4.0 requires Python 3.9 or later. The v1.3.2 fixes changed two defaults: mutual_coupling_matrix_measured now implements the documented impedance formulation (the old behavior is available via formulation='voltage'), and the az/el transforms now round-trip exactly under the documented convention. The cos_exp_phi parameter is deprecated in favor of cos_q_polarized_element.