phased-array-systems 0.2.0__tar.gz → 0.3.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.
Files changed (76) hide show
  1. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/PKG-INFO +1 -1
  2. phased_array_systems-0.3.0/examples/03_radar_detection_trade.py +377 -0
  3. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/__about__.py +1 -1
  4. phased_array_systems-0.3.0/src/phased_array_systems/cli.py +376 -0
  5. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/constants.py +1 -0
  6. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/evaluate.py +4 -2
  7. phased_array_systems-0.3.0/src/phased_array_systems/models/radar/__init__.py +25 -0
  8. phased_array_systems-0.3.0/src/phased_array_systems/models/radar/detection.py +259 -0
  9. phased_array_systems-0.3.0/src/phased_array_systems/models/radar/equation.py +253 -0
  10. phased_array_systems-0.3.0/src/phased_array_systems/models/radar/integration.py +140 -0
  11. phased_array_systems-0.3.0/src/phased_array_systems/reports/__init__.py +11 -0
  12. phased_array_systems-0.3.0/src/phased_array_systems/reports/generator.py +167 -0
  13. phased_array_systems-0.3.0/src/phased_array_systems/reports/html.py +452 -0
  14. phased_array_systems-0.3.0/src/phased_array_systems/reports/markdown.py +256 -0
  15. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/scenarios/radar.py +22 -3
  16. phased_array_systems-0.3.0/tests/test_cli.py +210 -0
  17. phased_array_systems-0.3.0/tests/test_radar.py +365 -0
  18. phased_array_systems-0.3.0/tests/test_reports.py +283 -0
  19. phased_array_systems-0.2.0/src/phased_array_systems/cli.py +0 -25
  20. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/.github/workflows/ci.yml +0 -0
  21. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/.github/workflows/publish.yml +0 -0
  22. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/.gitignore +0 -0
  23. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/CLAUDE.md +0 -0
  24. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/LICENSE +0 -0
  25. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/README.md +0 -0
  26. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/examples/01_comms_single_case.py +0 -0
  27. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/examples/02_comms_doe_trade.py +0 -0
  28. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/examples/configs/comms_basic.yaml +0 -0
  29. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/examples/configs/comms_doe.yaml +0 -0
  30. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/notebooks/tutorial_phased_array_trade_study.ipynb +0 -0
  31. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/pyproject.toml +0 -0
  32. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/ruff.toml +0 -0
  33. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/__init__.py +0 -0
  34. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/architecture/__init__.py +0 -0
  35. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/architecture/config.py +0 -0
  36. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/io/__init__.py +0 -0
  37. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/io/config_loader.py +0 -0
  38. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/io/exporters.py +0 -0
  39. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/io/schema.py +0 -0
  40. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/__init__.py +0 -0
  41. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/antenna/__init__.py +0 -0
  42. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/antenna/adapter.py +0 -0
  43. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/antenna/metrics.py +0 -0
  44. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/base.py +0 -0
  45. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/comms/__init__.py +0 -0
  46. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/comms/link_budget.py +0 -0
  47. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/comms/propagation.py +0 -0
  48. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/swapc/__init__.py +0 -0
  49. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/swapc/cost.py +0 -0
  50. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/swapc/power.py +0 -0
  51. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/requirements/__init__.py +0 -0
  52. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/requirements/core.py +0 -0
  53. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/scenarios/__init__.py +0 -0
  54. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/scenarios/base.py +0 -0
  55. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/scenarios/comms.py +0 -0
  56. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/__init__.py +0 -0
  57. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/design_space.py +0 -0
  58. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/doe.py +0 -0
  59. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/pareto.py +0 -0
  60. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/runner.py +0 -0
  61. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/types.py +0 -0
  62. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/utils/__init__.py +0 -0
  63. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/utils/hashing.py +0 -0
  64. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/viz/__init__.py +0 -0
  65. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/viz/plots.py +0 -0
  66. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/__init__.py +0 -0
  67. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_antenna_adapter.py +0 -0
  68. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_architecture.py +0 -0
  69. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_comms_link_budget.py +0 -0
  70. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_doe.py +0 -0
  71. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_evaluate.py +0 -0
  72. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_pareto.py +0 -0
  73. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_requirements.py +0 -0
  74. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_runner.py +0 -0
  75. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_swapc.py +0 -0
  76. {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_viz.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phased-array-systems
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Phased array antenna system design, optimization, and performance visualization
5
5
  Project-URL: Homepage, https://github.com/jman4162/phased-array-systems
6
6
  Project-URL: Documentation, https://github.com/jman4162/phased-array-systems#readme
@@ -0,0 +1,377 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example 03: Radar Detection Trade Study
4
+
5
+ This example demonstrates how to:
6
+ 1. Define a phased array radar architecture
7
+ 2. Define a radar detection scenario
8
+ 3. Define detection requirements
9
+ 4. Run a DOE trade study
10
+ 5. Extract Pareto-optimal designs
11
+ 6. Visualize the trade space
12
+
13
+ This shows the full workflow for radar system trade studies.
14
+ """
15
+
16
+ from pathlib import Path
17
+
18
+ import matplotlib
19
+
20
+ matplotlib.use("Agg") # Use non-interactive backend for headless environments
21
+
22
+ import matplotlib.pyplot as plt
23
+
24
+ from phased_array_systems.architecture import (
25
+ Architecture,
26
+ ArrayConfig,
27
+ CostConfig,
28
+ RFChainConfig,
29
+ )
30
+ from phased_array_systems.evaluate import evaluate_case_with_report
31
+ from phased_array_systems.io import export_results
32
+ from phased_array_systems.requirements import Requirement, RequirementSet
33
+ from phased_array_systems.scenarios import RadarDetectionScenario
34
+ from phased_array_systems.trades import (
35
+ BatchRunner,
36
+ DesignSpace,
37
+ extract_pareto,
38
+ filter_feasible,
39
+ generate_doe,
40
+ rank_pareto,
41
+ )
42
+ from phased_array_systems.viz import pareto_plot
43
+
44
+
45
+ def main():
46
+ print("=" * 70)
47
+ print("Phased Array Radar Detection Trade Study")
48
+ print("=" * 70)
49
+
50
+ # =========================================================================
51
+ # 1. Define the Scenario (fixed for all cases)
52
+ # =========================================================================
53
+ scenario = RadarDetectionScenario(
54
+ freq_hz=10e9, # 10 GHz (X-band)
55
+ bandwidth_hz=1e6, # 1 MHz
56
+ range_m=20e3, # 20 km target range
57
+ target_rcs_dbsm=10.0, # 10 m^2 target (10 dBsm) - small aircraft
58
+ pfa=1e-6, # Probability of false alarm
59
+ pd_required=0.9, # 90% probability of detection
60
+ n_pulses=16, # 16 pulses integrated
61
+ integration_type="noncoherent",
62
+ rx_noise_temp_k=290.0,
63
+ )
64
+
65
+ print(f"\nScenario: {scenario.freq_hz/1e9:.1f} GHz, {scenario.range_m/1e3:.0f} km range")
66
+ print(f" Target RCS: {scenario.target_rcs_dbsm} dBsm ({scenario.target_rcs_m2:.1f} m²)")
67
+ print(f" Detection: Pd={scenario.pd_required}, Pfa={scenario.pfa:.0e}")
68
+ print(f" Integration: {scenario.n_pulses} pulses ({scenario.integration_type})")
69
+
70
+ # =========================================================================
71
+ # 2. Define Requirements
72
+ # =========================================================================
73
+ requirements = RequirementSet(
74
+ requirements=[
75
+ Requirement(
76
+ id="REQ-001",
77
+ name="Detection Range",
78
+ metric_key="detection_range_m",
79
+ op=">=",
80
+ value=20000.0, # Must detect at 20 km
81
+ severity="must",
82
+ ),
83
+ Requirement(
84
+ id="REQ-002",
85
+ name="SNR Margin",
86
+ metric_key="snr_margin_db",
87
+ op=">=",
88
+ value=3.0, # 3 dB margin
89
+ severity="must",
90
+ ),
91
+ Requirement(
92
+ id="REQ-003",
93
+ name="Maximum Cost",
94
+ metric_key="cost_usd",
95
+ op="<=",
96
+ value=500000.0, # $500k max
97
+ severity="must",
98
+ ),
99
+ Requirement(
100
+ id="REQ-004",
101
+ name="Prime Power Limit",
102
+ metric_key="prime_power_w",
103
+ op="<=",
104
+ value=50000.0, # 50 kW max
105
+ severity="should",
106
+ ),
107
+ ],
108
+ name="Radar Requirements",
109
+ )
110
+
111
+ print(f"\nRequirements: {len(requirements)} defined")
112
+
113
+ # =========================================================================
114
+ # 3. Single Case Evaluation (baseline)
115
+ # =========================================================================
116
+ print("\n" + "-" * 70)
117
+ print("Baseline Design Evaluation")
118
+ print("-" * 70)
119
+
120
+ baseline_arch = Architecture(
121
+ array=ArrayConfig(nx=16, ny=16),
122
+ rf=RFChainConfig(tx_power_w_per_elem=10.0, pa_efficiency=0.35),
123
+ cost=CostConfig(cost_per_elem_usd=500.0, nre_usd=50000.0),
124
+ )
125
+
126
+ metrics, report = evaluate_case_with_report(baseline_arch, scenario, requirements)
127
+
128
+ print(f"\nBaseline: {baseline_arch.array.nx}x{baseline_arch.array.ny} array")
129
+ print(f" Peak Power: {metrics['peak_power_w']:.0f} W ({metrics['peak_power_dbw']:.1f} dBW)")
130
+ print(f" Antenna Gain: {metrics['g_ant_db']:.1f} dB")
131
+ print(f" SNR (single pulse): {metrics['snr_single_pulse_db']:.1f} dB")
132
+ print(f" SNR (integrated): {metrics['snr_integrated_db']:.1f} dB")
133
+ print(f" SNR Required: {metrics['snr_required_db']:.1f} dB")
134
+ print(f" SNR Margin: {metrics['snr_margin_db']:.1f} dB")
135
+ print(f" Detection Range: {metrics['detection_range_m']/1e3:.1f} km")
136
+ print(f" Cost: ${metrics['cost_usd']:,.0f}")
137
+ print(f"\n Requirements: {'PASS' if report.passes else 'FAIL'}")
138
+
139
+ # =========================================================================
140
+ # 4. Define Design Space
141
+ # =========================================================================
142
+ print("\n" + "-" * 70)
143
+ print("Setting up DOE Trade Study")
144
+ print("-" * 70)
145
+
146
+ design_space = (
147
+ DesignSpace(name="Radar Array Trade Space")
148
+ # Array dimensions (powers of 2)
149
+ .add_variable("array.nx", type="categorical", values=[8, 16, 32])
150
+ .add_variable("array.ny", type="categorical", values=[8, 16, 32])
151
+ .add_variable("array.enforce_subarray_constraint", type="categorical", values=[True])
152
+ # Fixed array parameters
153
+ .add_variable("array.geometry", type="categorical", values=["rectangular"])
154
+ .add_variable("array.dx_lambda", type="float", low=0.5, high=0.5)
155
+ .add_variable("array.dy_lambda", type="float", low=0.5, high=0.5)
156
+ # RF chain parameters (variable)
157
+ .add_variable("rf.tx_power_w_per_elem", type="float", low=5.0, high=20.0)
158
+ .add_variable("rf.pa_efficiency", type="float", low=0.25, high=0.45)
159
+ # Fixed RF parameters
160
+ .add_variable("rf.noise_figure_db", type="float", low=3.0, high=3.0)
161
+ .add_variable("rf.n_tx_beams", type="int", low=1, high=1)
162
+ .add_variable("rf.feed_loss_db", type="float", low=1.5, high=1.5)
163
+ .add_variable("rf.system_loss_db", type="float", low=0.0, high=0.0)
164
+ # Cost parameters (variable)
165
+ .add_variable("cost.cost_per_elem_usd", type="float", low=300.0, high=700.0)
166
+ .add_variable("cost.nre_usd", type="float", low=50000.0, high=50000.0)
167
+ .add_variable("cost.integration_cost_usd", type="float", low=20000.0, high=20000.0)
168
+ )
169
+
170
+ print(f"\nDesign Space: {design_space.n_dims} variables")
171
+ print(" Variable ranges:")
172
+ for var in design_space.variables:
173
+ if var.type == "categorical":
174
+ print(f" {var.name}: {var.values}")
175
+ elif var.low != var.high:
176
+ print(f" {var.name}: [{var.low}, {var.high}]")
177
+
178
+ # =========================================================================
179
+ # 5. Generate DOE
180
+ # =========================================================================
181
+ print("\n" + "-" * 70)
182
+ print("Generating DOE...")
183
+
184
+ n_samples = 100
185
+ doe = generate_doe(design_space, method="lhs", n_samples=n_samples, seed=42)
186
+
187
+ print(f"Generated {len(doe)} cases using Latin Hypercube Sampling")
188
+
189
+ # =========================================================================
190
+ # 6. Run Batch Evaluation
191
+ # =========================================================================
192
+ print("\n" + "-" * 70)
193
+ print("Running batch evaluation...")
194
+
195
+ runner = BatchRunner(scenario, requirements)
196
+
197
+ def progress_callback(completed, total):
198
+ pct = completed / total * 100
199
+ if completed % 20 == 0 or completed == total:
200
+ print(f" Progress: {completed}/{total} ({pct:.0f}%)")
201
+
202
+ results = runner.run(doe, n_workers=1, progress_callback=progress_callback)
203
+
204
+ # Check for errors
205
+ n_errors = results["meta.error"].notna().sum()
206
+ if n_errors > 0:
207
+ print(f"\nWarning: {n_errors} cases had errors")
208
+
209
+ # =========================================================================
210
+ # 7. Filter Feasible Designs
211
+ # =========================================================================
212
+ print("\n" + "-" * 70)
213
+ print("Filtering feasible designs...")
214
+
215
+ feasible = filter_feasible(results, requirements)
216
+ n_feasible = len(feasible)
217
+ n_total = len(results)
218
+ feasible_pct = n_feasible / n_total * 100
219
+
220
+ print(f"Feasible designs: {n_feasible}/{n_total} ({feasible_pct:.1f}%)")
221
+
222
+ # Create feasibility mask for plotting
223
+ feasible_mask = results["verification.passes"] == 1.0
224
+
225
+ # =========================================================================
226
+ # 8. Extract Pareto Frontier
227
+ # =========================================================================
228
+ print("\n" + "-" * 70)
229
+ print("Extracting Pareto frontier...")
230
+
231
+ # Minimize cost, maximize detection range
232
+ objectives = [
233
+ ("cost_usd", "minimize"),
234
+ ("detection_range_m", "maximize"),
235
+ ]
236
+
237
+ pareto = extract_pareto(feasible, objectives)
238
+ print(f"Pareto-optimal designs: {len(pareto)}")
239
+
240
+ # Rank Pareto points
241
+ ranked_pareto = rank_pareto(pareto, objectives, weights=[0.5, 0.5])
242
+
243
+ print("\nTop 5 Pareto-optimal designs:")
244
+ print("-" * 70)
245
+ top_5 = ranked_pareto.head(5)
246
+ for _, row in top_5.iterrows():
247
+ print(
248
+ f" {row['case_id']}: "
249
+ f"Array={int(row['array.nx'])}x{int(row['array.ny'])}, "
250
+ f"Range={row['detection_range_m']/1e3:.1f} km, "
251
+ f"Margin={row['snr_margin_db']:.1f} dB, "
252
+ f"Cost=${row['cost_usd']:,.0f}"
253
+ )
254
+
255
+ # =========================================================================
256
+ # 9. Visualize Results
257
+ # =========================================================================
258
+ print("\n" + "-" * 70)
259
+ print("Generating visualizations...")
260
+
261
+ # Create output directory
262
+ output_dir = Path("./results/radar_doe")
263
+ output_dir.mkdir(parents=True, exist_ok=True)
264
+
265
+ # Pareto plot: Cost vs Detection Range
266
+ fig1 = pareto_plot(
267
+ results,
268
+ x="cost_usd",
269
+ y="detection_range_m",
270
+ pareto_front=pareto,
271
+ feasible_mask=feasible_mask,
272
+ color_by="snr_margin_db",
273
+ title="Cost vs Detection Range Trade Space",
274
+ x_label="Total Cost (USD)",
275
+ y_label="Detection Range (m)",
276
+ )
277
+ fig1.savefig(output_dir / "pareto_cost_range.png", dpi=150, bbox_inches="tight")
278
+ print(" Saved: pareto_cost_range.png")
279
+
280
+ # Pareto plot: Cost vs SNR Margin
281
+ pareto_snr = extract_pareto(
282
+ feasible, [("cost_usd", "minimize"), ("snr_margin_db", "maximize")]
283
+ )
284
+ fig2 = pareto_plot(
285
+ results,
286
+ x="cost_usd",
287
+ y="snr_margin_db",
288
+ pareto_front=pareto_snr,
289
+ feasible_mask=feasible_mask,
290
+ color_by="detection_range_m",
291
+ title="Cost vs SNR Margin Trade Space",
292
+ x_label="Total Cost (USD)",
293
+ y_label="SNR Margin (dB)",
294
+ )
295
+ fig2.savefig(output_dir / "pareto_cost_snr.png", dpi=150, bbox_inches="tight")
296
+ print(" Saved: pareto_cost_snr.png")
297
+
298
+ # Power vs Detection Range
299
+ fig3, ax3 = plt.subplots(figsize=(10, 7))
300
+ scatter = ax3.scatter(
301
+ feasible["prime_power_w"],
302
+ feasible["detection_range_m"] / 1e3,
303
+ c=feasible["cost_usd"],
304
+ cmap="viridis",
305
+ s=60,
306
+ alpha=0.7,
307
+ )
308
+ ax3.scatter(
309
+ pareto["prime_power_w"],
310
+ pareto["detection_range_m"] / 1e3,
311
+ facecolors="none",
312
+ edgecolors="red",
313
+ s=150,
314
+ linewidths=2,
315
+ label="Pareto Optimal",
316
+ )
317
+ cbar = plt.colorbar(scatter, ax=ax3)
318
+ cbar.set_label("Cost (USD)")
319
+ ax3.set_xlabel("Prime Power (W)", fontsize=12)
320
+ ax3.set_ylabel("Detection Range (km)", fontsize=12)
321
+ ax3.set_title("Power vs Detection Range", fontsize=14)
322
+ ax3.legend()
323
+ ax3.grid(True, alpha=0.3)
324
+ fig3.savefig(output_dir / "power_vs_range.png", dpi=150, bbox_inches="tight")
325
+ print(" Saved: power_vs_range.png")
326
+
327
+ plt.close("all")
328
+
329
+ # =========================================================================
330
+ # 10. Export Results
331
+ # =========================================================================
332
+ print("\n" + "-" * 70)
333
+ print("Exporting results...")
334
+
335
+ export_results(results, output_dir / "all_results.parquet")
336
+ print(f" Saved: all_results.parquet ({len(results)} cases)")
337
+
338
+ export_results(feasible, output_dir / "feasible_results.parquet")
339
+ print(f" Saved: feasible_results.parquet ({len(feasible)} cases)")
340
+
341
+ export_results(ranked_pareto, output_dir / "pareto_front.csv", format="csv")
342
+ print(f" Saved: pareto_front.csv ({len(ranked_pareto)} cases)")
343
+
344
+ # =========================================================================
345
+ # Summary
346
+ # =========================================================================
347
+ print("\n" + "=" * 70)
348
+ print("Trade Study Summary")
349
+ print("=" * 70)
350
+ print(f" Total cases evaluated: {n_total}")
351
+ print(f" Feasible designs: {n_feasible} ({feasible_pct:.1f}%)")
352
+ print(f" Pareto-optimal designs: {len(pareto)}")
353
+ print(f"\n Output directory: {output_dir.absolute()}")
354
+
355
+ # Best designs
356
+ if len(ranked_pareto) > 0:
357
+ best = ranked_pareto.iloc[0]
358
+ print("\n Best compromise design (equal weights):")
359
+ print(f" Case ID: {best['case_id']}")
360
+ print(
361
+ f" Array size: {int(best['array.nx'])}x{int(best['array.ny'])} "
362
+ f"({int(best['n_elements'])} elements)"
363
+ )
364
+ print(f" TX power/element: {best['rf.tx_power_w_per_elem']:.1f} W")
365
+ print(f" Peak power: {best['peak_power_w']:.0f} W")
366
+ print(f" SNR margin: {best['snr_margin_db']:.1f} dB")
367
+ print(f" Detection range: {best['detection_range_m']/1e3:.1f} km")
368
+ print(f" Total cost: ${best['cost_usd']:,.0f}")
369
+ print(f" Prime power: {best['prime_power_w']:.0f} W")
370
+
371
+ print("\n" + "=" * 70)
372
+
373
+ return results, pareto, ranked_pareto
374
+
375
+
376
+ if __name__ == "__main__":
377
+ results, pareto, ranked = main()
@@ -1,4 +1,4 @@
1
1
  """Version and metadata for phased-array-systems."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
4
4
  __author__ = "phased-array-systems contributors"