phased-array-modeling 1.1.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phased_array_modeling-1.1.0/phased_array_modeling.egg-info → phased_array_modeling-1.2.0}/PKG-INFO +1 -1
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array/__init__.py +26 -4
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array/visualization.py +214 -0
- phased_array_modeling-1.2.0/phased_array/wideband.py +732 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0/phased_array_modeling.egg-info}/PKG-INFO +1 -1
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array_modeling.egg-info/SOURCES.txt +3 -1
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/pyproject.toml +1 -1
- phased_array_modeling-1.2.0/tests/test_wideband.py +333 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/LICENSE +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/README.md +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array/beamforming.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array/core.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array/export.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array/geometry.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array/impairments.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array/utils.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array_modeling.egg-info/dependency_links.txt +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array_modeling.egg-info/requires.txt +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/phased_array_modeling.egg-info/top_level.txt +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/setup.cfg +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/tests/test_beamforming.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/tests/test_core.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/tests/test_export.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/tests/test_geometry.py +0 -0
- {phased_array_modeling-1.1.0 → phased_array_modeling-1.2.0}/tests/test_impairments.py +0 -0
|
@@ -32,7 +32,7 @@ Example
|
|
|
32
32
|
>>> theta, phi, pattern_dB = pa.compute_full_pattern(geom.x, geom.y, weights, k)
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
__version__ = "1.
|
|
35
|
+
__version__ = "1.2.0"
|
|
36
36
|
|
|
37
37
|
# Beamforming functions
|
|
38
38
|
from .beamforming import ( # Amplitude tapers; Null steering; Multiple beams
|
|
@@ -81,13 +81,20 @@ from .utils import (azel_to_thetaphi, create_theta_phi_grid, create_uv_grid,
|
|
|
81
81
|
normalize_pattern, rad2deg, theta_phi_to_uv,
|
|
82
82
|
thetaphi_to_azel, uv_to_theta_phi, wavelength_to_k)
|
|
83
83
|
# Visualization functions
|
|
84
|
-
from .visualization import ( # 2D matplotlib plots; UV-space; 3D Plotly plots;
|
|
84
|
+
from .visualization import ( # 2D matplotlib plots; UV-space; 3D Plotly plots; Wideband
|
|
85
85
|
compute_pattern_uv_space, create_pattern_animation_plotly,
|
|
86
|
-
plot_array_geometry, plot_array_geometry_3d_plotly,
|
|
86
|
+
plot_array_geometry, plot_array_geometry_3d_plotly, plot_beam_squint,
|
|
87
87
|
plot_comparison_patterns, plot_pattern_2d,
|
|
88
88
|
plot_pattern_3d_cartesian_plotly, plot_pattern_3d_plotly,
|
|
89
89
|
plot_pattern_contour, plot_pattern_polar, plot_pattern_uv_plotly,
|
|
90
|
-
plot_pattern_uv_space
|
|
90
|
+
plot_pattern_uv_space, plot_pattern_vs_frequency,
|
|
91
|
+
plot_pattern_vs_frequency_plotly, plot_subarray_delays)
|
|
92
|
+
# Wideband / TTD functions
|
|
93
|
+
from .wideband import (analyze_instantaneous_bandwidth, compare_steering_modes,
|
|
94
|
+
compute_beam_squint, compute_pattern_vs_frequency,
|
|
95
|
+
compute_subarray_delays_ttd,
|
|
96
|
+
compute_subarray_weights_hybrid, steering_delays_ttd,
|
|
97
|
+
steering_vector_hybrid, steering_vector_ttd)
|
|
91
98
|
|
|
92
99
|
__all__ = [
|
|
93
100
|
# Version
|
|
@@ -200,4 +207,19 @@ __all__ = [
|
|
|
200
207
|
"load_pattern_npz",
|
|
201
208
|
"export_coupling_matrix_csv",
|
|
202
209
|
"export_summary_report",
|
|
210
|
+
# Wideband / TTD
|
|
211
|
+
"steering_vector_ttd",
|
|
212
|
+
"steering_delays_ttd",
|
|
213
|
+
"steering_vector_hybrid",
|
|
214
|
+
"compute_subarray_delays_ttd",
|
|
215
|
+
"compute_beam_squint",
|
|
216
|
+
"analyze_instantaneous_bandwidth",
|
|
217
|
+
"compute_pattern_vs_frequency",
|
|
218
|
+
"compute_subarray_weights_hybrid",
|
|
219
|
+
"compare_steering_modes",
|
|
220
|
+
# Wideband visualization
|
|
221
|
+
"plot_beam_squint",
|
|
222
|
+
"plot_pattern_vs_frequency",
|
|
223
|
+
"plot_pattern_vs_frequency_plotly",
|
|
224
|
+
"plot_subarray_delays",
|
|
203
225
|
]
|
|
@@ -823,3 +823,217 @@ def create_pattern_animation_plotly(
|
|
|
823
823
|
)
|
|
824
824
|
|
|
825
825
|
return fig
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
# ============== Wideband / Beam Squint Plots ==============
|
|
829
|
+
|
|
830
|
+
def plot_beam_squint(
|
|
831
|
+
frequencies: np.ndarray,
|
|
832
|
+
squint_data: Dict[str, np.ndarray],
|
|
833
|
+
center_frequency: float,
|
|
834
|
+
title: str = "Beam Squint vs Frequency",
|
|
835
|
+
figsize: Tuple[int, int] = (10, 6)
|
|
836
|
+
) -> Any:
|
|
837
|
+
"""
|
|
838
|
+
Plot beam squint comparison for different steering modes.
|
|
839
|
+
|
|
840
|
+
Parameters
|
|
841
|
+
----------
|
|
842
|
+
frequencies : ndarray
|
|
843
|
+
Frequency values in Hz
|
|
844
|
+
squint_data : dict
|
|
845
|
+
Dictionary of {mode_name: squint_array} in degrees
|
|
846
|
+
center_frequency : float
|
|
847
|
+
Center frequency in Hz (for normalization)
|
|
848
|
+
title : str
|
|
849
|
+
Plot title
|
|
850
|
+
figsize : tuple
|
|
851
|
+
Figure size
|
|
852
|
+
|
|
853
|
+
Returns
|
|
854
|
+
-------
|
|
855
|
+
ax : matplotlib axis
|
|
856
|
+
"""
|
|
857
|
+
if not MATPLOTLIB_AVAILABLE:
|
|
858
|
+
raise ImportError("matplotlib is required for this function")
|
|
859
|
+
|
|
860
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
861
|
+
|
|
862
|
+
# Normalize frequency to percentage of center
|
|
863
|
+
freq_percent = (frequencies - center_frequency) / center_frequency * 100
|
|
864
|
+
|
|
865
|
+
colors = {'phase': 'red', 'hybrid': 'blue', 'ttd': 'green'}
|
|
866
|
+
labels = {'phase': 'Phase-only', 'hybrid': 'Hybrid (TTD + Phase)', 'ttd': 'True-Time Delay'}
|
|
867
|
+
|
|
868
|
+
for mode, squint in squint_data.items():
|
|
869
|
+
color = colors.get(mode, None)
|
|
870
|
+
label = labels.get(mode, mode)
|
|
871
|
+
ax.plot(freq_percent, squint, 'o-', label=label, color=color, linewidth=2, markersize=6)
|
|
872
|
+
|
|
873
|
+
ax.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
874
|
+
ax.axvline(0, color='gray', linestyle='--', alpha=0.5)
|
|
875
|
+
|
|
876
|
+
ax.set_xlabel('Frequency Offset (%)')
|
|
877
|
+
ax.set_ylabel('Beam Squint (degrees)')
|
|
878
|
+
ax.set_title(title)
|
|
879
|
+
ax.grid(True, alpha=0.3)
|
|
880
|
+
ax.legend()
|
|
881
|
+
|
|
882
|
+
return ax
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def plot_pattern_vs_frequency(
|
|
886
|
+
angles: np.ndarray,
|
|
887
|
+
frequencies: np.ndarray,
|
|
888
|
+
patterns: np.ndarray,
|
|
889
|
+
center_frequency: float,
|
|
890
|
+
title: str = "Pattern vs Frequency",
|
|
891
|
+
min_dB: float = -40.0,
|
|
892
|
+
figsize: Tuple[int, int] = (12, 8)
|
|
893
|
+
) -> Any:
|
|
894
|
+
"""
|
|
895
|
+
Plot radiation patterns at multiple frequencies as a waterfall/heatmap.
|
|
896
|
+
|
|
897
|
+
Parameters
|
|
898
|
+
----------
|
|
899
|
+
angles : ndarray
|
|
900
|
+
Angle values in degrees
|
|
901
|
+
frequencies : ndarray
|
|
902
|
+
Frequency values in Hz
|
|
903
|
+
patterns : ndarray
|
|
904
|
+
2D array (n_freq x n_angles) of patterns in dB
|
|
905
|
+
center_frequency : float
|
|
906
|
+
Center frequency for labeling
|
|
907
|
+
title : str
|
|
908
|
+
Plot title
|
|
909
|
+
min_dB : float
|
|
910
|
+
Minimum dB for colormap
|
|
911
|
+
figsize : tuple
|
|
912
|
+
Figure size
|
|
913
|
+
|
|
914
|
+
Returns
|
|
915
|
+
-------
|
|
916
|
+
ax : matplotlib axis
|
|
917
|
+
"""
|
|
918
|
+
if not MATPLOTLIB_AVAILABLE:
|
|
919
|
+
raise ImportError("matplotlib is required for this function")
|
|
920
|
+
|
|
921
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
922
|
+
|
|
923
|
+
# Normalize frequency to percentage
|
|
924
|
+
freq_percent = (frequencies - center_frequency) / center_frequency * 100
|
|
925
|
+
|
|
926
|
+
patterns_clipped = np.clip(patterns, min_dB, 0)
|
|
927
|
+
|
|
928
|
+
im = ax.pcolormesh(angles, freq_percent, patterns_clipped, cmap='jet', shading='auto')
|
|
929
|
+
plt.colorbar(im, ax=ax, label='Gain (dB)')
|
|
930
|
+
|
|
931
|
+
ax.set_xlabel('Angle (degrees)')
|
|
932
|
+
ax.set_ylabel('Frequency Offset (%)')
|
|
933
|
+
ax.set_title(title)
|
|
934
|
+
|
|
935
|
+
return ax
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def plot_pattern_vs_frequency_plotly(
|
|
939
|
+
angles: np.ndarray,
|
|
940
|
+
frequencies: np.ndarray,
|
|
941
|
+
patterns: np.ndarray,
|
|
942
|
+
center_frequency: float,
|
|
943
|
+
title: str = "Pattern vs Frequency",
|
|
944
|
+
min_dB: float = -40.0
|
|
945
|
+
) -> Any:
|
|
946
|
+
"""
|
|
947
|
+
Interactive Plotly plot of patterns vs frequency.
|
|
948
|
+
|
|
949
|
+
Parameters
|
|
950
|
+
----------
|
|
951
|
+
angles : ndarray
|
|
952
|
+
Angle values in degrees
|
|
953
|
+
frequencies : ndarray
|
|
954
|
+
Frequency values in Hz
|
|
955
|
+
patterns : ndarray
|
|
956
|
+
2D array (n_freq x n_angles) in dB
|
|
957
|
+
center_frequency : float
|
|
958
|
+
Center frequency
|
|
959
|
+
title : str
|
|
960
|
+
Plot title
|
|
961
|
+
min_dB : float
|
|
962
|
+
Minimum dB
|
|
963
|
+
|
|
964
|
+
Returns
|
|
965
|
+
-------
|
|
966
|
+
fig : plotly Figure
|
|
967
|
+
"""
|
|
968
|
+
if not PLOTLY_AVAILABLE:
|
|
969
|
+
raise ImportError("plotly is required for this function")
|
|
970
|
+
|
|
971
|
+
freq_percent = (frequencies - center_frequency) / center_frequency * 100
|
|
972
|
+
patterns_clipped = np.clip(patterns, min_dB, 0)
|
|
973
|
+
|
|
974
|
+
fig = go.Figure(data=go.Heatmap(
|
|
975
|
+
x=angles,
|
|
976
|
+
y=freq_percent,
|
|
977
|
+
z=patterns_clipped,
|
|
978
|
+
colorscale='Jet',
|
|
979
|
+
zmin=min_dB,
|
|
980
|
+
zmax=0,
|
|
981
|
+
colorbar=dict(title='Gain (dB)')
|
|
982
|
+
))
|
|
983
|
+
|
|
984
|
+
fig.update_layout(
|
|
985
|
+
title=title,
|
|
986
|
+
xaxis_title='Angle (degrees)',
|
|
987
|
+
yaxis_title='Frequency Offset (%)'
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
return fig
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def plot_subarray_delays(
|
|
994
|
+
architecture,
|
|
995
|
+
delays: np.ndarray,
|
|
996
|
+
title: str = "Subarray Time Delays",
|
|
997
|
+
figsize: Tuple[int, int] = (10, 8)
|
|
998
|
+
) -> Any:
|
|
999
|
+
"""
|
|
1000
|
+
Visualize TTD values across subarrays.
|
|
1001
|
+
|
|
1002
|
+
Parameters
|
|
1003
|
+
----------
|
|
1004
|
+
architecture : SubarrayArchitecture
|
|
1005
|
+
Subarray architecture with centers
|
|
1006
|
+
delays : ndarray
|
|
1007
|
+
Time delay for each subarray in seconds
|
|
1008
|
+
title : str
|
|
1009
|
+
Plot title
|
|
1010
|
+
figsize : tuple
|
|
1011
|
+
Figure size
|
|
1012
|
+
|
|
1013
|
+
Returns
|
|
1014
|
+
-------
|
|
1015
|
+
ax : matplotlib axis
|
|
1016
|
+
"""
|
|
1017
|
+
if not MATPLOTLIB_AVAILABLE:
|
|
1018
|
+
raise ImportError("matplotlib is required for this function")
|
|
1019
|
+
|
|
1020
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
1021
|
+
|
|
1022
|
+
centers = architecture.subarray_centers
|
|
1023
|
+
delays_ns = delays * 1e9 # Convert to nanoseconds
|
|
1024
|
+
|
|
1025
|
+
scatter = ax.scatter(centers[:, 0], centers[:, 1], c=delays_ns,
|
|
1026
|
+
cmap='viridis', s=200, edgecolors='black')
|
|
1027
|
+
plt.colorbar(scatter, ax=ax, label='Delay (ns)')
|
|
1028
|
+
|
|
1029
|
+
# Add labels
|
|
1030
|
+
for i, (x, y) in enumerate(centers):
|
|
1031
|
+
ax.annotate(f'SA{i}', (x, y), ha='center', va='center', fontsize=8)
|
|
1032
|
+
|
|
1033
|
+
ax.set_xlabel('X (m)')
|
|
1034
|
+
ax.set_ylabel('Y (m)')
|
|
1035
|
+
ax.set_title(title)
|
|
1036
|
+
ax.set_aspect('equal')
|
|
1037
|
+
ax.grid(True, alpha=0.3)
|
|
1038
|
+
|
|
1039
|
+
return ax
|