phased-array-systems 0.1.0__py3-none-any.whl
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_systems/__about__.py +4 -0
- phased_array_systems/__init__.py +10 -0
- phased_array_systems/architecture/__init__.py +15 -0
- phased_array_systems/architecture/config.py +152 -0
- phased_array_systems/cli.py +25 -0
- phased_array_systems/constants.py +55 -0
- phased_array_systems/evaluate.py +136 -0
- phased_array_systems/io/__init__.py +13 -0
- phased_array_systems/io/config_loader.py +86 -0
- phased_array_systems/io/exporters.py +171 -0
- phased_array_systems/io/schema.py +145 -0
- phased_array_systems/models/__init__.py +5 -0
- phased_array_systems/models/antenna/__init__.py +15 -0
- phased_array_systems/models/antenna/adapter.py +190 -0
- phased_array_systems/models/antenna/metrics.py +166 -0
- phased_array_systems/models/base.py +30 -0
- phased_array_systems/models/comms/__init__.py +9 -0
- phased_array_systems/models/comms/link_budget.py +171 -0
- phased_array_systems/models/comms/propagation.py +84 -0
- phased_array_systems/models/swapc/__init__.py +9 -0
- phased_array_systems/models/swapc/cost.py +98 -0
- phased_array_systems/models/swapc/power.py +102 -0
- phased_array_systems/requirements/__init__.py +15 -0
- phased_array_systems/requirements/core.py +244 -0
- phased_array_systems/scenarios/__init__.py +11 -0
- phased_array_systems/scenarios/base.py +30 -0
- phased_array_systems/scenarios/comms.py +56 -0
- phased_array_systems/scenarios/radar.py +42 -0
- phased_array_systems/trades/__init__.py +16 -0
- phased_array_systems/trades/design_space.py +241 -0
- phased_array_systems/trades/doe.py +146 -0
- phased_array_systems/trades/pareto.py +266 -0
- phased_array_systems/trades/runner.py +245 -0
- phased_array_systems/types.py +54 -0
- phased_array_systems/utils/__init__.py +8 -0
- phased_array_systems/utils/hashing.py +70 -0
- phased_array_systems/viz/__init__.py +9 -0
- phased_array_systems/viz/plots.py +324 -0
- phased_array_systems-0.1.0.dist-info/METADATA +174 -0
- phased_array_systems-0.1.0.dist-info/RECORD +43 -0
- phased_array_systems-0.1.0.dist-info/WHEEL +4 -0
- phased_array_systems-0.1.0.dist-info/entry_points.txt +2 -0
- phased_array_systems-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Type aliases and protocols for the phased-array-systems package."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal, Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
# Type aliases for metric dictionaries
|
|
6
|
+
MetricsDict = dict[str, float | str | None]
|
|
7
|
+
|
|
8
|
+
# Comparison operators for requirements
|
|
9
|
+
ComparisonOp = Literal[">=", "<=", "==", ">", "<"]
|
|
10
|
+
|
|
11
|
+
# Severity levels for requirements
|
|
12
|
+
Severity = Literal["must", "should", "nice"]
|
|
13
|
+
|
|
14
|
+
# Geometry types supported by phased-array-modeling
|
|
15
|
+
GeometryType = Literal["rectangular", "circular", "triangular"]
|
|
16
|
+
|
|
17
|
+
# Path loss model types
|
|
18
|
+
PathLossModel = Literal["fspl"]
|
|
19
|
+
|
|
20
|
+
# DOE sampling methods
|
|
21
|
+
SamplingMethod = Literal["grid", "random", "lhs"]
|
|
22
|
+
|
|
23
|
+
# Optimization directions
|
|
24
|
+
OptimizeDirection = Literal["minimize", "maximize"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@runtime_checkable
|
|
28
|
+
class Scenario(Protocol):
|
|
29
|
+
"""Protocol for scenario objects (comms, radar, etc.)."""
|
|
30
|
+
|
|
31
|
+
freq_hz: float
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@runtime_checkable
|
|
35
|
+
class ModelBlock(Protocol):
|
|
36
|
+
"""Protocol for model blocks that evaluate architecture/scenario combinations.
|
|
37
|
+
|
|
38
|
+
All model blocks must implement evaluate() and return a flat metrics dictionary.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
|
|
43
|
+
def evaluate(self, arch: Any, scenario: Scenario, context: dict[str, Any]) -> MetricsDict:
|
|
44
|
+
"""Evaluate the model and return metrics.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
arch: Architecture configuration object
|
|
48
|
+
scenario: Scenario configuration object
|
|
49
|
+
context: Additional context (e.g., results from other models)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Dictionary of metric_name -> value pairs
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Hashing utilities for stable case identification."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compute_config_hash(config: dict[str, Any], length: int = 8) -> str:
|
|
9
|
+
"""Compute a stable hash for a configuration dictionary.
|
|
10
|
+
|
|
11
|
+
Uses JSON serialization with sorted keys to ensure consistent
|
|
12
|
+
ordering, then SHA-256 hashing.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
config: Configuration dictionary
|
|
16
|
+
length: Number of hex characters to return
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Hexadecimal hash string
|
|
20
|
+
"""
|
|
21
|
+
# Serialize with sorted keys for consistency
|
|
22
|
+
json_str = json.dumps(config, sort_keys=True, default=str)
|
|
23
|
+
hash_bytes = hashlib.sha256(json_str.encode()).hexdigest()
|
|
24
|
+
return hash_bytes[:length]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def compute_case_id(
|
|
28
|
+
case_index: int,
|
|
29
|
+
config: dict[str, Any] | None = None,
|
|
30
|
+
prefix: str = "case",
|
|
31
|
+
) -> str:
|
|
32
|
+
"""Compute a unique case identifier.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
case_index: Sequential case number
|
|
36
|
+
config: Optional configuration to include in hash
|
|
37
|
+
prefix: Prefix for the case ID
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Case ID string like "case_00001" or "case_00001_a1b2c3d4"
|
|
41
|
+
"""
|
|
42
|
+
base_id = f"{prefix}_{case_index:05d}"
|
|
43
|
+
|
|
44
|
+
if config is not None:
|
|
45
|
+
config_hash = compute_config_hash(config)
|
|
46
|
+
return f"{base_id}_{config_hash}"
|
|
47
|
+
|
|
48
|
+
return base_id
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def dict_to_hashable(d: dict[str, Any]) -> tuple:
|
|
52
|
+
"""Convert a dictionary to a hashable tuple representation.
|
|
53
|
+
|
|
54
|
+
Useful for using dictionaries as dictionary keys or in sets.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
d: Dictionary to convert
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Tuple of (key, value) pairs, sorted by key
|
|
61
|
+
"""
|
|
62
|
+
items = []
|
|
63
|
+
for k in sorted(d.keys()):
|
|
64
|
+
v = d[k]
|
|
65
|
+
if isinstance(v, dict):
|
|
66
|
+
v = dict_to_hashable(v)
|
|
67
|
+
elif isinstance(v, list):
|
|
68
|
+
v = tuple(v)
|
|
69
|
+
items.append((k, v))
|
|
70
|
+
return tuple(items)
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Plotting functions for trade study visualization."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def pareto_plot(
|
|
11
|
+
results: pd.DataFrame,
|
|
12
|
+
x: str,
|
|
13
|
+
y: str,
|
|
14
|
+
pareto_front: pd.DataFrame | None = None,
|
|
15
|
+
feasible_mask: pd.Series | None = None,
|
|
16
|
+
color_by: str | None = None,
|
|
17
|
+
size_by: str | None = None,
|
|
18
|
+
ax: plt.Axes | None = None,
|
|
19
|
+
title: str | None = None,
|
|
20
|
+
x_label: str | None = None,
|
|
21
|
+
y_label: str | None = None,
|
|
22
|
+
show_pareto_line: bool = True,
|
|
23
|
+
figsize: tuple[float, float] = (8, 6),
|
|
24
|
+
) -> plt.Figure:
|
|
25
|
+
"""Create a Pareto plot showing trade-offs between two objectives.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
results: DataFrame with all evaluation results
|
|
29
|
+
x: Column name for x-axis
|
|
30
|
+
y: Column name for y-axis
|
|
31
|
+
pareto_front: Optional DataFrame with Pareto-optimal points to highlight
|
|
32
|
+
feasible_mask: Optional boolean Series marking feasible designs
|
|
33
|
+
color_by: Optional column name to color points by
|
|
34
|
+
size_by: Optional column name to size points by
|
|
35
|
+
ax: Optional existing Axes to plot on
|
|
36
|
+
title: Plot title
|
|
37
|
+
x_label: X-axis label (defaults to column name)
|
|
38
|
+
y_label: Y-axis label (defaults to column name)
|
|
39
|
+
show_pareto_line: If True, draw line connecting Pareto points
|
|
40
|
+
figsize: Figure size (width, height)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
matplotlib Figure object
|
|
44
|
+
"""
|
|
45
|
+
if ax is None:
|
|
46
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
47
|
+
else:
|
|
48
|
+
fig = ax.figure
|
|
49
|
+
|
|
50
|
+
# Determine point sizes
|
|
51
|
+
if size_by is not None and size_by in results.columns:
|
|
52
|
+
sizes = results[size_by]
|
|
53
|
+
sizes = (sizes - sizes.min()) / (sizes.max() - sizes.min() + 1e-10) * 100 + 20
|
|
54
|
+
else:
|
|
55
|
+
sizes = 50
|
|
56
|
+
|
|
57
|
+
# Determine colors
|
|
58
|
+
if color_by is not None and color_by in results.columns:
|
|
59
|
+
colors = results[color_by]
|
|
60
|
+
cmap = plt.cm.viridis
|
|
61
|
+
else:
|
|
62
|
+
colors = "steelblue"
|
|
63
|
+
cmap = None
|
|
64
|
+
|
|
65
|
+
# Plot infeasible points (if mask provided)
|
|
66
|
+
if feasible_mask is not None:
|
|
67
|
+
infeasible = results[~feasible_mask]
|
|
68
|
+
if len(infeasible) > 0:
|
|
69
|
+
ax.scatter(
|
|
70
|
+
infeasible[x],
|
|
71
|
+
infeasible[y],
|
|
72
|
+
c="lightgray",
|
|
73
|
+
s=30,
|
|
74
|
+
alpha=0.5,
|
|
75
|
+
marker="x",
|
|
76
|
+
label="Infeasible",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Plot all feasible points
|
|
80
|
+
plot_data = results[feasible_mask] if feasible_mask is not None else results
|
|
81
|
+
|
|
82
|
+
scatter = ax.scatter(
|
|
83
|
+
plot_data[x],
|
|
84
|
+
plot_data[y],
|
|
85
|
+
c=colors if color_by is None else plot_data[color_by],
|
|
86
|
+
s=sizes if isinstance(sizes, int) else plot_data[size_by] if size_by else 50,
|
|
87
|
+
alpha=0.7,
|
|
88
|
+
cmap=cmap,
|
|
89
|
+
label="Feasible" if feasible_mask is not None else None,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Add colorbar if coloring by a metric
|
|
93
|
+
if color_by is not None and cmap is not None:
|
|
94
|
+
cbar = plt.colorbar(scatter, ax=ax)
|
|
95
|
+
cbar.set_label(color_by)
|
|
96
|
+
|
|
97
|
+
# Highlight Pareto front
|
|
98
|
+
if pareto_front is not None and len(pareto_front) > 0:
|
|
99
|
+
ax.scatter(
|
|
100
|
+
pareto_front[x],
|
|
101
|
+
pareto_front[y],
|
|
102
|
+
facecolors="none",
|
|
103
|
+
edgecolors="red",
|
|
104
|
+
s=100,
|
|
105
|
+
linewidths=2,
|
|
106
|
+
label="Pareto Optimal",
|
|
107
|
+
zorder=5,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Draw Pareto line
|
|
111
|
+
if show_pareto_line and len(pareto_front) > 1:
|
|
112
|
+
sorted_pareto = pareto_front.sort_values(x)
|
|
113
|
+
ax.plot(
|
|
114
|
+
sorted_pareto[x],
|
|
115
|
+
sorted_pareto[y],
|
|
116
|
+
"r--",
|
|
117
|
+
alpha=0.5,
|
|
118
|
+
linewidth=1.5,
|
|
119
|
+
zorder=4,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Labels and title
|
|
123
|
+
ax.set_xlabel(x_label or x)
|
|
124
|
+
ax.set_ylabel(y_label or y)
|
|
125
|
+
ax.set_title(title or f"Pareto Trade-off: {x} vs {y}")
|
|
126
|
+
|
|
127
|
+
# Legend
|
|
128
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
129
|
+
if handles:
|
|
130
|
+
ax.legend(loc="best")
|
|
131
|
+
|
|
132
|
+
ax.grid(True, alpha=0.3)
|
|
133
|
+
fig.tight_layout()
|
|
134
|
+
|
|
135
|
+
return fig
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def scatter_matrix(
|
|
139
|
+
results: pd.DataFrame,
|
|
140
|
+
columns: list[str],
|
|
141
|
+
color_by: str | None = None,
|
|
142
|
+
diagonal: Literal["hist", "kde"] = "hist",
|
|
143
|
+
figsize: tuple[float, float] | None = None,
|
|
144
|
+
title: str | None = None,
|
|
145
|
+
) -> plt.Figure:
|
|
146
|
+
"""Create a scatter matrix showing pairwise relationships.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
results: DataFrame with evaluation results
|
|
150
|
+
columns: List of column names to include
|
|
151
|
+
color_by: Optional column name to color points by
|
|
152
|
+
diagonal: What to show on diagonal ("hist" or "kde")
|
|
153
|
+
figsize: Figure size (auto-calculated if None)
|
|
154
|
+
title: Overall figure title
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
matplotlib Figure object
|
|
158
|
+
"""
|
|
159
|
+
n = len(columns)
|
|
160
|
+
if figsize is None:
|
|
161
|
+
figsize = (n * 2.5, n * 2.5)
|
|
162
|
+
|
|
163
|
+
fig, axes = plt.subplots(n, n, figsize=figsize)
|
|
164
|
+
|
|
165
|
+
# Determine colors
|
|
166
|
+
if color_by is not None and color_by in results.columns:
|
|
167
|
+
colors = results[color_by].values
|
|
168
|
+
cmap = plt.cm.viridis
|
|
169
|
+
norm = plt.Normalize(colors.min(), colors.max())
|
|
170
|
+
point_colors = cmap(norm(colors))
|
|
171
|
+
else:
|
|
172
|
+
point_colors = "steelblue"
|
|
173
|
+
|
|
174
|
+
for i, col_i in enumerate(columns):
|
|
175
|
+
for j, col_j in enumerate(columns):
|
|
176
|
+
ax = axes[i, j]
|
|
177
|
+
|
|
178
|
+
if i == j:
|
|
179
|
+
# Diagonal: histogram or KDE
|
|
180
|
+
if diagonal == "hist":
|
|
181
|
+
ax.hist(results[col_i], bins=20, alpha=0.7, color="steelblue")
|
|
182
|
+
else: # kde
|
|
183
|
+
from scipy import stats
|
|
184
|
+
|
|
185
|
+
data = results[col_i].dropna()
|
|
186
|
+
if len(data) > 1:
|
|
187
|
+
kde = stats.gaussian_kde(data)
|
|
188
|
+
x_range = np.linspace(data.min(), data.max(), 100)
|
|
189
|
+
ax.fill_between(x_range, kde(x_range), alpha=0.5)
|
|
190
|
+
ax.plot(x_range, kde(x_range), color="steelblue")
|
|
191
|
+
else:
|
|
192
|
+
# Off-diagonal: scatter plot
|
|
193
|
+
ax.scatter(
|
|
194
|
+
results[col_j],
|
|
195
|
+
results[col_i],
|
|
196
|
+
c=point_colors,
|
|
197
|
+
s=10,
|
|
198
|
+
alpha=0.5,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Labels on edges only
|
|
202
|
+
if i == n - 1:
|
|
203
|
+
ax.set_xlabel(col_j, fontsize=8)
|
|
204
|
+
else:
|
|
205
|
+
ax.set_xticklabels([])
|
|
206
|
+
|
|
207
|
+
if j == 0:
|
|
208
|
+
ax.set_ylabel(col_i, fontsize=8)
|
|
209
|
+
else:
|
|
210
|
+
ax.set_yticklabels([])
|
|
211
|
+
|
|
212
|
+
ax.tick_params(labelsize=6)
|
|
213
|
+
|
|
214
|
+
if title:
|
|
215
|
+
fig.suptitle(title, fontsize=12)
|
|
216
|
+
|
|
217
|
+
fig.tight_layout()
|
|
218
|
+
if title:
|
|
219
|
+
fig.subplots_adjust(top=0.95)
|
|
220
|
+
|
|
221
|
+
return fig
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def trade_space_plot(
|
|
225
|
+
results: pd.DataFrame,
|
|
226
|
+
x: str,
|
|
227
|
+
y: str,
|
|
228
|
+
z: str,
|
|
229
|
+
feasible_mask: pd.Series | None = None,
|
|
230
|
+
pareto_front: pd.DataFrame | None = None,
|
|
231
|
+
ax: plt.Axes | None = None,
|
|
232
|
+
figsize: tuple[float, float] = (10, 8),
|
|
233
|
+
title: str | None = None,
|
|
234
|
+
) -> plt.Figure:
|
|
235
|
+
"""Create a 3D trade space visualization.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
results: DataFrame with evaluation results
|
|
239
|
+
x: Column name for x-axis
|
|
240
|
+
y: Column name for y-axis
|
|
241
|
+
z: Column name for z-axis (color)
|
|
242
|
+
feasible_mask: Optional boolean mask for feasible designs
|
|
243
|
+
pareto_front: Optional DataFrame with Pareto-optimal points
|
|
244
|
+
ax: Optional existing 3D Axes
|
|
245
|
+
figsize: Figure size
|
|
246
|
+
title: Plot title
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
matplotlib Figure object
|
|
250
|
+
"""
|
|
251
|
+
if ax is None:
|
|
252
|
+
fig = plt.figure(figsize=figsize)
|
|
253
|
+
ax = fig.add_subplot(111, projection="3d")
|
|
254
|
+
else:
|
|
255
|
+
fig = ax.figure
|
|
256
|
+
|
|
257
|
+
# Plot points
|
|
258
|
+
if feasible_mask is not None:
|
|
259
|
+
# Infeasible
|
|
260
|
+
inf_data = results[~feasible_mask]
|
|
261
|
+
ax.scatter(
|
|
262
|
+
inf_data[x],
|
|
263
|
+
inf_data[y],
|
|
264
|
+
inf_data[z],
|
|
265
|
+
c="lightgray",
|
|
266
|
+
alpha=0.3,
|
|
267
|
+
s=20,
|
|
268
|
+
marker="x",
|
|
269
|
+
)
|
|
270
|
+
plot_data = results[feasible_mask]
|
|
271
|
+
else:
|
|
272
|
+
plot_data = results
|
|
273
|
+
|
|
274
|
+
scatter = ax.scatter(
|
|
275
|
+
plot_data[x],
|
|
276
|
+
plot_data[y],
|
|
277
|
+
plot_data[z],
|
|
278
|
+
c=plot_data[z],
|
|
279
|
+
cmap="viridis",
|
|
280
|
+
alpha=0.7,
|
|
281
|
+
s=40,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Highlight Pareto front
|
|
285
|
+
if pareto_front is not None and len(pareto_front) > 0:
|
|
286
|
+
ax.scatter(
|
|
287
|
+
pareto_front[x],
|
|
288
|
+
pareto_front[y],
|
|
289
|
+
pareto_front[z],
|
|
290
|
+
c="red",
|
|
291
|
+
s=100,
|
|
292
|
+
marker="*",
|
|
293
|
+
label="Pareto Optimal",
|
|
294
|
+
zorder=5,
|
|
295
|
+
)
|
|
296
|
+
ax.legend()
|
|
297
|
+
|
|
298
|
+
ax.set_xlabel(x)
|
|
299
|
+
ax.set_ylabel(y)
|
|
300
|
+
ax.set_zlabel(z)
|
|
301
|
+
|
|
302
|
+
fig.colorbar(scatter, ax=ax, label=z, shrink=0.5)
|
|
303
|
+
|
|
304
|
+
if title:
|
|
305
|
+
ax.set_title(title)
|
|
306
|
+
|
|
307
|
+
return fig
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def save_figure(
|
|
311
|
+
fig: plt.Figure,
|
|
312
|
+
path: str,
|
|
313
|
+
dpi: int = 150,
|
|
314
|
+
transparent: bool = False,
|
|
315
|
+
) -> None:
|
|
316
|
+
"""Save a figure to file.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
fig: matplotlib Figure to save
|
|
320
|
+
path: Output path (extension determines format)
|
|
321
|
+
dpi: Resolution for raster formats
|
|
322
|
+
transparent: Whether to use transparent background
|
|
323
|
+
"""
|
|
324
|
+
fig.savefig(path, dpi=dpi, transparent=transparent, bbox_inches="tight")
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phased-array-systems
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Phased array antenna system design, optimization, and performance visualization
|
|
5
|
+
Project-URL: Homepage, https://github.com/jman4162/phased-array-systems
|
|
6
|
+
Project-URL: Documentation, https://github.com/jman4162/phased-array-systems#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/jman4162/phased-array-systems
|
|
8
|
+
Project-URL: Issues, https://github.com/jman4162/phased-array-systems/issues
|
|
9
|
+
Author: phased-array-systems contributors
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: RF,antenna,communications,optimization,phased array,radar,system design,trade study
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: matplotlib>=3.7.0
|
|
26
|
+
Requires-Dist: numpy>=1.24.0
|
|
27
|
+
Requires-Dist: pandas>=2.0.0
|
|
28
|
+
Requires-Dist: phased-array-modeling>=1.2.0
|
|
29
|
+
Requires-Dist: pydantic>=2.0
|
|
30
|
+
Requires-Dist: pyyaml>=6.0
|
|
31
|
+
Requires-Dist: scipy>=1.10.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pandas-stubs>=2.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
38
|
+
Provides-Extra: plotting
|
|
39
|
+
Requires-Dist: kaleido>=0.2; extra == 'plotting'
|
|
40
|
+
Requires-Dist: plotly>=5.0; extra == 'plotting'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# phased-array-systems
|
|
44
|
+
|
|
45
|
+
[](https://github.com/jman4162/phased-array-systems/actions/workflows/ci.yml)
|
|
46
|
+
[](https://badge.fury.io/py/phased-array-systems)
|
|
47
|
+
[](https://www.python.org/downloads/)
|
|
48
|
+
[](https://opensource.org/licenses/MIT)
|
|
49
|
+
|
|
50
|
+
Phased array antenna system design, optimization, and performance visualization for wireless communications and radar applications.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Requirements as first-class objects**: Every run produces pass/fail + margins with traceability
|
|
55
|
+
- **Trade-space exploration**: DOE + Pareto optimization over single-point designs
|
|
56
|
+
- **Flat metrics dictionary**: All models return consistent `dict[str, float]` for interchange
|
|
57
|
+
- **Config-driven reproducibility**: Stable case IDs, seed control, version stamping
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install phased-array-systems
|
|
63
|
+
|
|
64
|
+
# Development dependencies
|
|
65
|
+
pip install phased-array-systems[dev]
|
|
66
|
+
|
|
67
|
+
# Visualization extras
|
|
68
|
+
pip install phased-array-systems[plotting]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
### Single Case Evaluation
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from phased_array_systems.architecture import Architecture, ArrayConfig, RFChainConfig
|
|
77
|
+
from phased_array_systems.scenarios import CommsLinkScenario
|
|
78
|
+
from phased_array_systems.evaluate import evaluate_case
|
|
79
|
+
|
|
80
|
+
# Define architecture
|
|
81
|
+
arch = Architecture(
|
|
82
|
+
array=ArrayConfig(nx=8, ny=8, dx_lambda=0.5, dy_lambda=0.5),
|
|
83
|
+
rf=RFChainConfig(tx_power_w_per_elem=1.0, pa_efficiency=0.3),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Define scenario
|
|
87
|
+
scenario = CommsLinkScenario(
|
|
88
|
+
freq_hz=10e9,
|
|
89
|
+
bandwidth_hz=10e6,
|
|
90
|
+
range_m=100e3,
|
|
91
|
+
required_snr_db=10.0,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Evaluate
|
|
95
|
+
metrics = evaluate_case(arch, scenario)
|
|
96
|
+
print(f"EIRP: {metrics['eirp_dbw']:.1f} dBW")
|
|
97
|
+
print(f"Link Margin: {metrics['link_margin_db']:.1f} dB")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### DOE Trade Study
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from phased_array_systems.trades import DesignSpace, generate_doe, BatchRunner, extract_pareto
|
|
104
|
+
|
|
105
|
+
# Define design space
|
|
106
|
+
space = (
|
|
107
|
+
DesignSpace()
|
|
108
|
+
.add_variable("array.nx", "int", low=4, high=16)
|
|
109
|
+
.add_variable("array.ny", "int", low=4, high=16)
|
|
110
|
+
.add_variable("rf.tx_power_w_per_elem", "float", low=0.5, high=3.0)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Generate DOE
|
|
114
|
+
doe = generate_doe(space, method="lhs", n_samples=100, seed=42)
|
|
115
|
+
|
|
116
|
+
# Run batch evaluation
|
|
117
|
+
runner = BatchRunner(scenario)
|
|
118
|
+
results = runner.run(doe)
|
|
119
|
+
|
|
120
|
+
# Extract Pareto frontier
|
|
121
|
+
pareto = extract_pareto(results, [
|
|
122
|
+
("cost_usd", "minimize"),
|
|
123
|
+
("eirp_dbw", "maximize"),
|
|
124
|
+
])
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Examples
|
|
128
|
+
|
|
129
|
+
See the `examples/` directory:
|
|
130
|
+
- `01_comms_single_case.py` - Single case evaluation
|
|
131
|
+
- `02_comms_doe_trade.py` - Full DOE trade study workflow
|
|
132
|
+
|
|
133
|
+
### Tutorial Notebook
|
|
134
|
+
|
|
135
|
+
[](https://colab.research.google.com/github/jman4162/phased-array-systems/blob/main/notebooks/tutorial_phased_array_trade_study.ipynb)
|
|
136
|
+
|
|
137
|
+
Try the interactive tutorial in Google Colab!
|
|
138
|
+
|
|
139
|
+
## Package Structure
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
phased_array_systems/
|
|
143
|
+
├── architecture/ # Array, RF chain, cost configurations
|
|
144
|
+
├── scenarios/ # CommsLinkScenario, RadarDetectionScenario
|
|
145
|
+
├── requirements/ # Requirement definitions and verification
|
|
146
|
+
├── models/
|
|
147
|
+
│ ├── antenna/ # Phased array adapter and metrics
|
|
148
|
+
│ ├── comms/ # Link budget, propagation models
|
|
149
|
+
│ └── swapc/ # Power and cost models
|
|
150
|
+
├── trades/ # DOE, batch runner, Pareto analysis
|
|
151
|
+
├── viz/ # Plotting utilities
|
|
152
|
+
└── io/ # Config loading, results export
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Clone the repository
|
|
159
|
+
git clone https://github.com/jman4162/phased-array-systems.git
|
|
160
|
+
cd phased-array-systems
|
|
161
|
+
|
|
162
|
+
# Install in development mode
|
|
163
|
+
pip install -e ".[dev]"
|
|
164
|
+
|
|
165
|
+
# Run tests
|
|
166
|
+
pytest tests/ -v
|
|
167
|
+
|
|
168
|
+
# Run linting
|
|
169
|
+
ruff check .
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
phased_array_systems/__about__.py,sha256=Qe_T9JvLe3C-9zBcXDIoQjsjy25E-ox3VlYm2AK2GTg,125
|
|
2
|
+
phased_array_systems/__init__.py,sha256=47SV42pztafqI3b0kVwjfQxdp8eKoEuda69PD9oXgVM,362
|
|
3
|
+
phased_array_systems/cli.py,sha256=x_D3sGeOeo9RpDLkOBTX8bhfpmATieUAgii_uS8MISw,642
|
|
4
|
+
phased_array_systems/constants.py,sha256=-WjHG4CJXmckxJsG2ymVGrqbTtWFKvAdARe3S03Ybjw,1310
|
|
5
|
+
phased_array_systems/evaluate.py,sha256=EgQXtM9zztAr9QHM6wcschA055eH6rY3i9xft10s-7c,4825
|
|
6
|
+
phased_array_systems/types.py,sha256=_K-9wvl2UO5jzjZgvHWw3MC-Aiz2WDfdgLV6IT6llK0,1521
|
|
7
|
+
phased_array_systems/architecture/__init__.py,sha256=ksS18SuMWiWxO4ov8fx13LYV4sn2-R9c67IrTyfpLoM,265
|
|
8
|
+
phased_array_systems/architecture/config.py,sha256=oc8bOKrGuJsUp5ZKLVCIE2KZQ3-P70q26naVy2J1u5s,5274
|
|
9
|
+
phased_array_systems/io/__init__.py,sha256=j8h7-fYUKTiFknqHTxdUIJJQaaXHmyKf76f_pyH4fZQ,387
|
|
10
|
+
phased_array_systems/io/config_loader.py,sha256=eEeEO5nqUewXrjs5ZlQDCeo_E5eIVjCQer0C4Pvpot0,2470
|
|
11
|
+
phased_array_systems/io/exporters.py,sha256=m6f4ZhT6cQaJLE3v1zMVbatKQRlOUViaAnYzAZTE3rY,4441
|
|
12
|
+
phased_array_systems/io/schema.py,sha256=43_O9U_w8ALjAYL64XajHV_zwq-TeRqP7zL8WFveYaI,4858
|
|
13
|
+
phased_array_systems/models/__init__.py,sha256=kjl_doYE2uyGRnjxrJzC0Pp_UcdjZY_GeY9GHWh9iOs,123
|
|
14
|
+
phased_array_systems/models/base.py,sha256=AP6C70tU8jXKxMCiuvB4oy5Vw2KFX_CTfK5oiYjrGo0,937
|
|
15
|
+
phased_array_systems/models/antenna/__init__.py,sha256=N1SEfVY1IuT0kEuq5H-1wk4hZwWRJeVp1TKnJlgy4Uk,394
|
|
16
|
+
phased_array_systems/models/antenna/adapter.py,sha256=jkoGnQ0d5DMASK9iRodosaGa6gM-bF3syhJKSFyTynQ,6670
|
|
17
|
+
phased_array_systems/models/antenna/metrics.py,sha256=cw2DCZ_vGpT9vNcurdSSxVB9zV9524fNM5nxrsWiwro,4875
|
|
18
|
+
phased_array_systems/models/comms/__init__.py,sha256=7aPkt-zKpe6yECynehGwAoP14I62n-M5w1lkMGgaiRM,243
|
|
19
|
+
phased_array_systems/models/comms/link_budget.py,sha256=dGKO7VA5C8RI5lffzqDs6H7l7fujfNe5HM5mgeTUfHc,6017
|
|
20
|
+
phased_array_systems/models/comms/propagation.py,sha256=mXsmgCAZ3h3FZIk0L9NykQga_fCHXVXBFSm2oRq_Xks,2364
|
|
21
|
+
phased_array_systems/models/swapc/__init__.py,sha256=0UCAWinqnMVW6c_num8qYTVHF3EYsJHGoOh44xKjd-I,228
|
|
22
|
+
phased_array_systems/models/swapc/cost.py,sha256=5c_vHDZXjmANoBbB2sTYotzNPgirIoGFKSBlfsqbPtA,2898
|
|
23
|
+
phased_array_systems/models/swapc/power.py,sha256=f7ECkfnM8MvlW4po-TboeWU6kyqrcmix_2IWTJsK7LY,3187
|
|
24
|
+
phased_array_systems/requirements/__init__.py,sha256=4eNoTS1ySN0toEYLAo1ttpe3u6Bu8YXFOUP0k4GTwUc,302
|
|
25
|
+
phased_array_systems/requirements/core.py,sha256=E3Bp474UXLyMhYLrlmERa0jaP3hl8iskgCvAJmVhJ70,8095
|
|
26
|
+
phased_array_systems/scenarios/__init__.py,sha256=4LAbI_zz3oQt36-WSxkHFSV1rRtTy13aG1s8YgodTvc,361
|
|
27
|
+
phased_array_systems/scenarios/base.py,sha256=Mlq-hMob1an4TviLQfFekRsQfCyOsNFJwCavKjrDaxs,818
|
|
28
|
+
phased_array_systems/scenarios/comms.py,sha256=d-IubHyWZC5iFZLUJdkwoDkMWAnptQn0bvkvN1KDBj4,2216
|
|
29
|
+
phased_array_systems/scenarios/radar.py,sha256=1DHMT8Mjrtg_hLUWuvh0iHQJomJT1Ct4Az9EWa_jF5I,1576
|
|
30
|
+
phased_array_systems/trades/__init__.py,sha256=3YIx1FvSwJWq9ai7zpFA7Ss2AiwcUJMAbZxuOCqxhjY,514
|
|
31
|
+
phased_array_systems/trades/design_space.py,sha256=mVH5FcBTjQmhr5IqucLlcCaLIZYcqvSKElowY8YvARw,8524
|
|
32
|
+
phased_array_systems/trades/doe.py,sha256=bEj946EcC8NqP0QQ6zxAw0rqEhAIMmDd3M3biNCm9Ow,4836
|
|
33
|
+
phased_array_systems/trades/pareto.py,sha256=4kyXP30SlEACbWyXk1mVBccGsG6mFXcQfPjYigsrxZk,8588
|
|
34
|
+
phased_array_systems/trades/runner.py,sha256=_4Wac8QS7alZAjijz5kIaWiRY54zGvXIVqP0av4gfAo,8225
|
|
35
|
+
phased_array_systems/utils/__init__.py,sha256=gGnUsnK_3Yv_7mG86dNIlV0PdQzR8lYuCvBab7P-hlE,200
|
|
36
|
+
phased_array_systems/utils/hashing.py,sha256=vNuMvM9Mh_qtvfIpiGvyKor2FjWMVAeGqNWjrCpjysI,1836
|
|
37
|
+
phased_array_systems/viz/__init__.py,sha256=ofsSeB8GVu_YbpSNJiu8UdoIxfO2GeIbChIp8hhRBhc,225
|
|
38
|
+
phased_array_systems/viz/plots.py,sha256=bAcBVMlLCv7Mj5bT8m6k6mGQmEhqsLGB-R6X8qs-U84,9128
|
|
39
|
+
phased_array_systems-0.1.0.dist-info/METADATA,sha256=z2RgzLRfESqAN8CZGIUWqLHmI-8t0DCiPxnhYVO9-9c,5826
|
|
40
|
+
phased_array_systems-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
41
|
+
phased_array_systems-0.1.0.dist-info/entry_points.txt,sha256=C5I-JkVj-j99qHvWW3PoXWjnzam62a0N5W4haHHxXxM,56
|
|
42
|
+
phased_array_systems-0.1.0.dist-info/licenses/LICENSE,sha256=tTGISmKHj6ZUsuU7scIo5pEognjuz4a7IB3maC4DfBw,1067
|
|
43
|
+
phased_array_systems-0.1.0.dist-info/RECORD,,
|