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,171 @@
|
|
|
1
|
+
"""Communications link budget model."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from phased_array_systems.architecture import Architecture
|
|
7
|
+
from phased_array_systems.constants import K_B, W_TO_DBW
|
|
8
|
+
from phased_array_systems.models.comms.propagation import compute_fspl
|
|
9
|
+
from phased_array_systems.scenarios import CommsLinkScenario
|
|
10
|
+
from phased_array_systems.types import MetricsDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommsLinkModel:
|
|
14
|
+
"""Communications link budget calculator.
|
|
15
|
+
|
|
16
|
+
Computes EIRP, received power, noise power, SNR, and link margin
|
|
17
|
+
for a point-to-point or satellite communications link.
|
|
18
|
+
|
|
19
|
+
Link Budget Equations:
|
|
20
|
+
EIRP = P_tx_total + G_tx - L_tx
|
|
21
|
+
P_rx = EIRP - L_path + G_rx
|
|
22
|
+
N = 10*log10(k*T*B) + NF
|
|
23
|
+
SNR = P_rx - N
|
|
24
|
+
Margin = SNR - required_SNR
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
name: Model block name for identification
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name: str = "comms_link"
|
|
31
|
+
|
|
32
|
+
def evaluate(
|
|
33
|
+
self,
|
|
34
|
+
arch: Architecture,
|
|
35
|
+
scenario: CommsLinkScenario,
|
|
36
|
+
context: dict[str, Any],
|
|
37
|
+
) -> MetricsDict:
|
|
38
|
+
"""Evaluate communications link budget.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
arch: Architecture configuration
|
|
42
|
+
scenario: Communications link scenario
|
|
43
|
+
context: Additional context, may include antenna metrics:
|
|
44
|
+
- g_peak_db: Antenna gain (uses this if provided)
|
|
45
|
+
- scan_loss_db: Scan loss (uses this if provided)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dictionary with link budget metrics:
|
|
49
|
+
- tx_power_total_dbw: Total transmit power (dBW)
|
|
50
|
+
- tx_power_per_elem_dbw: TX power per element (dBW)
|
|
51
|
+
- g_tx_db: Transmit antenna gain (dB)
|
|
52
|
+
- eirp_dbw: Effective Isotropic Radiated Power (dBW)
|
|
53
|
+
- path_loss_db: Total path loss (dB)
|
|
54
|
+
- g_rx_db: Receive antenna gain (dB)
|
|
55
|
+
- rx_power_dbw: Received power (dBW)
|
|
56
|
+
- noise_power_dbw: Noise power (dBW)
|
|
57
|
+
- snr_rx_db: Received SNR (dB)
|
|
58
|
+
- link_margin_db: Link margin (dB)
|
|
59
|
+
"""
|
|
60
|
+
# Get transmit power
|
|
61
|
+
n_elements = arch.array.n_elements
|
|
62
|
+
tx_power_per_elem_w = arch.rf.tx_power_w_per_elem
|
|
63
|
+
tx_power_total_w = tx_power_per_elem_w * n_elements
|
|
64
|
+
tx_power_total_dbw = W_TO_DBW(tx_power_total_w)
|
|
65
|
+
tx_power_per_elem_dbw = W_TO_DBW(tx_power_per_elem_w)
|
|
66
|
+
|
|
67
|
+
# Get antenna gain from context or compute approximate
|
|
68
|
+
if "g_peak_db" in context:
|
|
69
|
+
g_tx_db = context["g_peak_db"]
|
|
70
|
+
else:
|
|
71
|
+
# Approximate gain for uniform array
|
|
72
|
+
# G = 4*pi*A/lambda^2 ≈ pi * nx * dx * ny * dy * 4
|
|
73
|
+
aperture_area_lambda_sq = (
|
|
74
|
+
arch.array.nx * arch.array.dx_lambda *
|
|
75
|
+
arch.array.ny * arch.array.dy_lambda
|
|
76
|
+
)
|
|
77
|
+
g_tx_linear = 4 * math.pi * aperture_area_lambda_sq
|
|
78
|
+
g_tx_db = 10 * math.log10(g_tx_linear)
|
|
79
|
+
|
|
80
|
+
# Apply scan loss if provided
|
|
81
|
+
if "scan_loss_db" in context:
|
|
82
|
+
g_tx_db -= context["scan_loss_db"]
|
|
83
|
+
|
|
84
|
+
# Transmit losses (feed network + system)
|
|
85
|
+
tx_loss_db = arch.rf.feed_loss_db + arch.rf.system_loss_db
|
|
86
|
+
|
|
87
|
+
# EIRP
|
|
88
|
+
eirp_dbw = tx_power_total_dbw + g_tx_db - tx_loss_db
|
|
89
|
+
|
|
90
|
+
# Path loss
|
|
91
|
+
if scenario.path_loss_model == "fspl":
|
|
92
|
+
path_loss_db = compute_fspl(scenario.freq_hz, scenario.range_m)
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError(f"Unknown path loss model: {scenario.path_loss_model}")
|
|
95
|
+
|
|
96
|
+
# Add extra losses
|
|
97
|
+
total_path_loss_db = path_loss_db + scenario.total_extra_loss_db
|
|
98
|
+
|
|
99
|
+
# Receive antenna gain (isotropic if not specified)
|
|
100
|
+
g_rx_db = scenario.rx_antenna_gain_db if scenario.rx_antenna_gain_db is not None else 0.0
|
|
101
|
+
|
|
102
|
+
# Received power
|
|
103
|
+
rx_power_dbw = eirp_dbw - total_path_loss_db + g_rx_db
|
|
104
|
+
|
|
105
|
+
# Noise power: N = k*T*B
|
|
106
|
+
noise_power_w = K_B * scenario.rx_noise_temp_k * scenario.bandwidth_hz
|
|
107
|
+
noise_power_dbw = W_TO_DBW(noise_power_w)
|
|
108
|
+
|
|
109
|
+
# Add receiver noise figure
|
|
110
|
+
noise_power_dbw += arch.rf.noise_figure_db
|
|
111
|
+
|
|
112
|
+
# SNR
|
|
113
|
+
snr_rx_db = rx_power_dbw - noise_power_dbw
|
|
114
|
+
|
|
115
|
+
# Link margin
|
|
116
|
+
link_margin_db = snr_rx_db - scenario.required_snr_db
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
"tx_power_total_dbw": tx_power_total_dbw,
|
|
120
|
+
"tx_power_per_elem_dbw": tx_power_per_elem_dbw,
|
|
121
|
+
"g_tx_db": g_tx_db,
|
|
122
|
+
"eirp_dbw": eirp_dbw,
|
|
123
|
+
"path_loss_db": total_path_loss_db,
|
|
124
|
+
"fspl_db": path_loss_db,
|
|
125
|
+
"g_rx_db": g_rx_db,
|
|
126
|
+
"rx_power_dbw": rx_power_dbw,
|
|
127
|
+
"noise_power_dbw": noise_power_dbw,
|
|
128
|
+
"snr_rx_db": snr_rx_db,
|
|
129
|
+
"link_margin_db": link_margin_db,
|
|
130
|
+
"required_snr_db": scenario.required_snr_db,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def compute_link_margin(
|
|
135
|
+
eirp_dbw: float,
|
|
136
|
+
path_loss_db: float,
|
|
137
|
+
g_rx_db: float,
|
|
138
|
+
noise_temp_k: float,
|
|
139
|
+
bandwidth_hz: float,
|
|
140
|
+
noise_figure_db: float,
|
|
141
|
+
required_snr_db: float,
|
|
142
|
+
) -> dict[str, float]:
|
|
143
|
+
"""Standalone link margin calculation.
|
|
144
|
+
|
|
145
|
+
Convenience function for quick link budget calculations
|
|
146
|
+
without full Architecture/Scenario objects.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
eirp_dbw: Effective Isotropic Radiated Power (dBW)
|
|
150
|
+
path_loss_db: Total path loss (dB)
|
|
151
|
+
g_rx_db: Receive antenna gain (dB)
|
|
152
|
+
noise_temp_k: System noise temperature (K)
|
|
153
|
+
bandwidth_hz: Signal bandwidth (Hz)
|
|
154
|
+
noise_figure_db: Receiver noise figure (dB)
|
|
155
|
+
required_snr_db: Required SNR for demodulation (dB)
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Dictionary with rx_power_dbw, noise_power_dbw, snr_db, margin_db
|
|
159
|
+
"""
|
|
160
|
+
rx_power_dbw = eirp_dbw - path_loss_db + g_rx_db
|
|
161
|
+
noise_power_w = K_B * noise_temp_k * bandwidth_hz
|
|
162
|
+
noise_power_dbw = W_TO_DBW(noise_power_w) + noise_figure_db
|
|
163
|
+
snr_db = rx_power_dbw - noise_power_dbw
|
|
164
|
+
margin_db = snr_db - required_snr_db
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"rx_power_dbw": rx_power_dbw,
|
|
168
|
+
"noise_power_dbw": noise_power_dbw,
|
|
169
|
+
"snr_db": snr_db,
|
|
170
|
+
"margin_db": margin_db,
|
|
171
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Propagation loss models for communications links."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
from phased_array_systems.constants import C
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compute_fspl(freq_hz: float, range_m: float) -> float:
|
|
9
|
+
"""Compute Free Space Path Loss (FSPL).
|
|
10
|
+
|
|
11
|
+
FSPL = 20*log10(4*pi*d*f/c)
|
|
12
|
+
= 20*log10(d) + 20*log10(f) + 20*log10(4*pi/c)
|
|
13
|
+
= 20*log10(d) + 20*log10(f) - 147.55 (with d in m, f in Hz)
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
freq_hz: Frequency in Hz
|
|
17
|
+
range_m: Range/distance in meters
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Free space path loss in dB (positive value)
|
|
21
|
+
"""
|
|
22
|
+
if freq_hz <= 0:
|
|
23
|
+
raise ValueError("Frequency must be positive")
|
|
24
|
+
if range_m <= 0:
|
|
25
|
+
raise ValueError("Range must be positive")
|
|
26
|
+
|
|
27
|
+
wavelength = C / freq_hz
|
|
28
|
+
fspl_linear = (4 * math.pi * range_m / wavelength) ** 2
|
|
29
|
+
return 10 * math.log10(fspl_linear)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def compute_fspl_wavelength(wavelength_m: float, range_m: float) -> float:
|
|
33
|
+
"""Compute FSPL given wavelength directly.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
wavelength_m: Wavelength in meters
|
|
37
|
+
range_m: Range/distance in meters
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Free space path loss in dB (positive value)
|
|
41
|
+
"""
|
|
42
|
+
if wavelength_m <= 0:
|
|
43
|
+
raise ValueError("Wavelength must be positive")
|
|
44
|
+
if range_m <= 0:
|
|
45
|
+
raise ValueError("Range must be positive")
|
|
46
|
+
|
|
47
|
+
fspl_linear = (4 * math.pi * range_m / wavelength_m) ** 2
|
|
48
|
+
return 10 * math.log10(fspl_linear)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def compute_two_ray_path_loss(
|
|
52
|
+
freq_hz: float,
|
|
53
|
+
range_m: float,
|
|
54
|
+
h_tx_m: float,
|
|
55
|
+
h_rx_m: float,
|
|
56
|
+
) -> float:
|
|
57
|
+
"""Compute two-ray ground reflection path loss.
|
|
58
|
+
|
|
59
|
+
At short ranges, behaves like FSPL. At long ranges (beyond crossover
|
|
60
|
+
distance), follows d^4 attenuation.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
freq_hz: Frequency in Hz
|
|
64
|
+
range_m: Horizontal range in meters
|
|
65
|
+
h_tx_m: Transmitter height in meters
|
|
66
|
+
h_rx_m: Receiver height in meters
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Path loss in dB (positive value)
|
|
70
|
+
"""
|
|
71
|
+
wavelength = C / freq_hz
|
|
72
|
+
|
|
73
|
+
# Crossover distance
|
|
74
|
+
d_cross = 4 * h_tx_m * h_rx_m / wavelength
|
|
75
|
+
|
|
76
|
+
if range_m < d_cross:
|
|
77
|
+
# Use FSPL in near region
|
|
78
|
+
return compute_fspl(freq_hz, range_m)
|
|
79
|
+
else:
|
|
80
|
+
# Two-ray model: PL = 40*log10(d) - 20*log10(ht*hr)
|
|
81
|
+
# Normalized to match FSPL at crossover
|
|
82
|
+
pl_cross = compute_fspl(freq_hz, d_cross)
|
|
83
|
+
pl_two_ray = pl_cross + 40 * math.log10(range_m / d_cross)
|
|
84
|
+
return pl_two_ray
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Cost models for phased array systems."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from phased_array_systems.architecture import Architecture
|
|
6
|
+
from phased_array_systems.types import MetricsDict, Scenario
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CostModel:
|
|
10
|
+
"""Parametric cost model for phased array systems.
|
|
11
|
+
|
|
12
|
+
Computes recurring and non-recurring costs based on
|
|
13
|
+
array size and cost parameters.
|
|
14
|
+
|
|
15
|
+
Cost Equations:
|
|
16
|
+
Recurring_cost = n_elements * cost_per_element
|
|
17
|
+
Total_cost = Recurring_cost + NRE + Integration
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
name: Model block name for identification
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
name: str = "cost"
|
|
24
|
+
|
|
25
|
+
def evaluate(
|
|
26
|
+
self,
|
|
27
|
+
arch: Architecture,
|
|
28
|
+
scenario: Scenario,
|
|
29
|
+
context: dict[str, Any],
|
|
30
|
+
) -> MetricsDict:
|
|
31
|
+
"""Evaluate cost metrics.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
arch: Architecture configuration
|
|
35
|
+
scenario: Scenario (unused for basic cost model)
|
|
36
|
+
context: Additional context (unused)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dictionary with cost metrics:
|
|
40
|
+
- recurring_cost_usd: Element-based recurring cost (USD)
|
|
41
|
+
- nre_usd: Non-recurring engineering cost (USD)
|
|
42
|
+
- integration_cost_usd: System integration cost (USD)
|
|
43
|
+
- total_cost_usd: Total system cost (USD)
|
|
44
|
+
- cost_per_element_usd: Cost per element (USD)
|
|
45
|
+
- n_elements: Number of elements
|
|
46
|
+
"""
|
|
47
|
+
n_elements = arch.array.n_elements
|
|
48
|
+
cost_per_elem = arch.cost.cost_per_elem_usd
|
|
49
|
+
nre = arch.cost.nre_usd
|
|
50
|
+
integration = arch.cost.integration_cost_usd
|
|
51
|
+
|
|
52
|
+
# Recurring cost (scales with elements)
|
|
53
|
+
recurring_cost = n_elements * cost_per_elem
|
|
54
|
+
|
|
55
|
+
# Total cost
|
|
56
|
+
total_cost = recurring_cost + nre + integration
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"recurring_cost_usd": recurring_cost,
|
|
60
|
+
"nre_usd": nre,
|
|
61
|
+
"integration_cost_usd": integration,
|
|
62
|
+
"total_cost_usd": total_cost,
|
|
63
|
+
"cost_per_element_usd": cost_per_elem,
|
|
64
|
+
"cost_usd": total_cost, # Canonical metric name
|
|
65
|
+
"n_elements": n_elements,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def compute_cost_per_watt(total_cost_usd: float, rf_power_w: float) -> float:
|
|
70
|
+
"""Compute cost per Watt of RF power.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
total_cost_usd: Total system cost (USD)
|
|
74
|
+
rf_power_w: RF output power (W)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Cost per Watt (USD/W)
|
|
78
|
+
"""
|
|
79
|
+
if rf_power_w <= 0:
|
|
80
|
+
return float("inf")
|
|
81
|
+
return total_cost_usd / rf_power_w
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def compute_cost_per_db(total_cost_usd: float, eirp_dbw: float) -> float:
|
|
85
|
+
"""Compute cost per dBW of EIRP.
|
|
86
|
+
|
|
87
|
+
Useful for comparing cost-effectiveness of different architectures.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
total_cost_usd: Total system cost (USD)
|
|
91
|
+
eirp_dbw: Effective Isotropic Radiated Power (dBW)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Cost per dBW (USD/dBW)
|
|
95
|
+
"""
|
|
96
|
+
if eirp_dbw <= 0:
|
|
97
|
+
return float("inf")
|
|
98
|
+
return total_cost_usd / eirp_dbw
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Power consumption models for phased array systems."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from phased_array_systems.architecture import Architecture
|
|
6
|
+
from phased_array_systems.types import MetricsDict, Scenario
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PowerModel:
|
|
10
|
+
"""Power consumption calculator for phased array systems.
|
|
11
|
+
|
|
12
|
+
Computes DC power, RF power, and prime power based on
|
|
13
|
+
architecture parameters and efficiency factors.
|
|
14
|
+
|
|
15
|
+
Power Equations:
|
|
16
|
+
RF_power = n_elements * tx_power_per_elem
|
|
17
|
+
DC_power = RF_power / pa_efficiency
|
|
18
|
+
Prime_power = DC_power * (1 + overhead_factor)
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
name: Model block name for identification
|
|
22
|
+
overhead_factor: Additional power overhead (cooling, control, etc.)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
name: str = "power"
|
|
26
|
+
|
|
27
|
+
def __init__(self, overhead_factor: float = 0.2):
|
|
28
|
+
"""Initialize power model.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
overhead_factor: Fraction of DC power for overhead (default 20%)
|
|
32
|
+
"""
|
|
33
|
+
self.overhead_factor = overhead_factor
|
|
34
|
+
|
|
35
|
+
def evaluate(
|
|
36
|
+
self,
|
|
37
|
+
arch: Architecture,
|
|
38
|
+
scenario: Scenario,
|
|
39
|
+
context: dict[str, Any],
|
|
40
|
+
) -> MetricsDict:
|
|
41
|
+
"""Evaluate power metrics.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
arch: Architecture configuration
|
|
45
|
+
scenario: Scenario (may influence duty cycle)
|
|
46
|
+
context: Additional context (unused)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dictionary with power metrics:
|
|
50
|
+
- rf_power_w: Total RF output power (W)
|
|
51
|
+
- dc_power_w: DC power consumption (W)
|
|
52
|
+
- prime_power_w: Prime/wall power (W)
|
|
53
|
+
- pa_efficiency: Power amplifier efficiency
|
|
54
|
+
- n_elements: Number of array elements
|
|
55
|
+
"""
|
|
56
|
+
n_elements = arch.array.n_elements
|
|
57
|
+
tx_power_per_elem = arch.rf.tx_power_w_per_elem
|
|
58
|
+
pa_efficiency = arch.rf.pa_efficiency
|
|
59
|
+
|
|
60
|
+
# RF power
|
|
61
|
+
rf_power_w = n_elements * tx_power_per_elem
|
|
62
|
+
|
|
63
|
+
# DC power (accounting for PA efficiency)
|
|
64
|
+
dc_power_w = rf_power_w / pa_efficiency
|
|
65
|
+
|
|
66
|
+
# Prime power (including overhead)
|
|
67
|
+
prime_power_w = dc_power_w * (1 + self.overhead_factor)
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
"rf_power_w": rf_power_w,
|
|
71
|
+
"dc_power_w": dc_power_w,
|
|
72
|
+
"prime_power_w": prime_power_w,
|
|
73
|
+
"pa_efficiency": pa_efficiency,
|
|
74
|
+
"n_elements": n_elements,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def compute_thermal_load(
|
|
79
|
+
dc_power_w: float,
|
|
80
|
+
rf_power_w: float,
|
|
81
|
+
additional_dissipation_w: float = 0.0,
|
|
82
|
+
) -> dict[str, float]:
|
|
83
|
+
"""Compute thermal dissipation for heat management.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
dc_power_w: Total DC power consumption (W)
|
|
87
|
+
rf_power_w: RF power radiated (W)
|
|
88
|
+
additional_dissipation_w: Other heat sources (W)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Dictionary with thermal metrics:
|
|
92
|
+
- heat_dissipation_w: Total heat to remove (W)
|
|
93
|
+
- rf_efficiency: Fraction of DC converted to RF
|
|
94
|
+
"""
|
|
95
|
+
# Heat = DC input - RF output + additional sources
|
|
96
|
+
heat_dissipation_w = dc_power_w - rf_power_w + additional_dissipation_w
|
|
97
|
+
rf_efficiency = rf_power_w / dc_power_w if dc_power_w > 0 else 0.0
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
"heat_dissipation_w": heat_dissipation_w,
|
|
101
|
+
"rf_efficiency": rf_efficiency,
|
|
102
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Requirements capture and verification subsystem."""
|
|
2
|
+
|
|
3
|
+
from phased_array_systems.requirements.core import (
|
|
4
|
+
Requirement,
|
|
5
|
+
RequirementResult,
|
|
6
|
+
RequirementSet,
|
|
7
|
+
VerificationReport,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Requirement",
|
|
12
|
+
"RequirementResult",
|
|
13
|
+
"RequirementSet",
|
|
14
|
+
"VerificationReport",
|
|
15
|
+
]
|