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.
Files changed (43) hide show
  1. phased_array_systems/__about__.py +4 -0
  2. phased_array_systems/__init__.py +10 -0
  3. phased_array_systems/architecture/__init__.py +15 -0
  4. phased_array_systems/architecture/config.py +152 -0
  5. phased_array_systems/cli.py +25 -0
  6. phased_array_systems/constants.py +55 -0
  7. phased_array_systems/evaluate.py +136 -0
  8. phased_array_systems/io/__init__.py +13 -0
  9. phased_array_systems/io/config_loader.py +86 -0
  10. phased_array_systems/io/exporters.py +171 -0
  11. phased_array_systems/io/schema.py +145 -0
  12. phased_array_systems/models/__init__.py +5 -0
  13. phased_array_systems/models/antenna/__init__.py +15 -0
  14. phased_array_systems/models/antenna/adapter.py +190 -0
  15. phased_array_systems/models/antenna/metrics.py +166 -0
  16. phased_array_systems/models/base.py +30 -0
  17. phased_array_systems/models/comms/__init__.py +9 -0
  18. phased_array_systems/models/comms/link_budget.py +171 -0
  19. phased_array_systems/models/comms/propagation.py +84 -0
  20. phased_array_systems/models/swapc/__init__.py +9 -0
  21. phased_array_systems/models/swapc/cost.py +98 -0
  22. phased_array_systems/models/swapc/power.py +102 -0
  23. phased_array_systems/requirements/__init__.py +15 -0
  24. phased_array_systems/requirements/core.py +244 -0
  25. phased_array_systems/scenarios/__init__.py +11 -0
  26. phased_array_systems/scenarios/base.py +30 -0
  27. phased_array_systems/scenarios/comms.py +56 -0
  28. phased_array_systems/scenarios/radar.py +42 -0
  29. phased_array_systems/trades/__init__.py +16 -0
  30. phased_array_systems/trades/design_space.py +241 -0
  31. phased_array_systems/trades/doe.py +146 -0
  32. phased_array_systems/trades/pareto.py +266 -0
  33. phased_array_systems/trades/runner.py +245 -0
  34. phased_array_systems/types.py +54 -0
  35. phased_array_systems/utils/__init__.py +8 -0
  36. phased_array_systems/utils/hashing.py +70 -0
  37. phased_array_systems/viz/__init__.py +9 -0
  38. phased_array_systems/viz/plots.py +324 -0
  39. phased_array_systems-0.1.0.dist-info/METADATA +174 -0
  40. phased_array_systems-0.1.0.dist-info/RECORD +43 -0
  41. phased_array_systems-0.1.0.dist-info/WHEEL +4 -0
  42. phased_array_systems-0.1.0.dist-info/entry_points.txt +2 -0
  43. 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,9 @@
1
+ """Size, Weight, Power, and Cost (SWaP-C) models."""
2
+
3
+ from phased_array_systems.models.swapc.cost import CostModel
4
+ from phased_array_systems.models.swapc.power import PowerModel
5
+
6
+ __all__ = [
7
+ "CostModel",
8
+ "PowerModel",
9
+ ]
@@ -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
+ ]