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.
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/PKG-INFO +1 -1
- phased_array_systems-0.3.0/examples/03_radar_detection_trade.py +377 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/__about__.py +1 -1
- phased_array_systems-0.3.0/src/phased_array_systems/cli.py +376 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/constants.py +1 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/evaluate.py +4 -2
- phased_array_systems-0.3.0/src/phased_array_systems/models/radar/__init__.py +25 -0
- phased_array_systems-0.3.0/src/phased_array_systems/models/radar/detection.py +259 -0
- phased_array_systems-0.3.0/src/phased_array_systems/models/radar/equation.py +253 -0
- phased_array_systems-0.3.0/src/phased_array_systems/models/radar/integration.py +140 -0
- phased_array_systems-0.3.0/src/phased_array_systems/reports/__init__.py +11 -0
- phased_array_systems-0.3.0/src/phased_array_systems/reports/generator.py +167 -0
- phased_array_systems-0.3.0/src/phased_array_systems/reports/html.py +452 -0
- phased_array_systems-0.3.0/src/phased_array_systems/reports/markdown.py +256 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/scenarios/radar.py +22 -3
- phased_array_systems-0.3.0/tests/test_cli.py +210 -0
- phased_array_systems-0.3.0/tests/test_radar.py +365 -0
- phased_array_systems-0.3.0/tests/test_reports.py +283 -0
- phased_array_systems-0.2.0/src/phased_array_systems/cli.py +0 -25
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/.github/workflows/ci.yml +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/.github/workflows/publish.yml +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/.gitignore +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/CLAUDE.md +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/LICENSE +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/README.md +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/examples/01_comms_single_case.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/examples/02_comms_doe_trade.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/examples/configs/comms_basic.yaml +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/examples/configs/comms_doe.yaml +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/notebooks/tutorial_phased_array_trade_study.ipynb +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/pyproject.toml +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/ruff.toml +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/architecture/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/architecture/config.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/io/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/io/config_loader.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/io/exporters.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/io/schema.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/antenna/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/antenna/adapter.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/antenna/metrics.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/base.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/comms/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/comms/link_budget.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/comms/propagation.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/swapc/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/swapc/cost.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/models/swapc/power.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/requirements/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/requirements/core.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/scenarios/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/scenarios/base.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/scenarios/comms.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/design_space.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/doe.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/pareto.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/trades/runner.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/types.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/utils/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/utils/hashing.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/viz/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/src/phased_array_systems/viz/plots.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/__init__.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_antenna_adapter.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_architecture.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_comms_link_budget.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_doe.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_evaluate.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_pareto.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_requirements.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_runner.py +0 -0
- {phased_array_systems-0.2.0 → phased_array_systems-0.3.0}/tests/test_swapc.py +0 -0
- {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.
|
|
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()
|