Skip to content

Communications Models API

Link budget calculations for communications systems.

Overview

from phased_array_systems.models.comms import CommsLinkModel, compute_fspl

# For direct link margin calculations
from phased_array_systems.models.comms.link_budget import compute_link_margin

Classes

Communications link budget calculator.

Computes EIRP, received power, noise power, SNR, and link margin for a point-to-point or satellite communications link.

ATTRIBUTE DESCRIPTION
name

Model block name for identification

TYPE: str

evaluate(arch: Architecture, scenario: CommsLinkScenario, context: dict[str, Any]) -> MetricsDict

Evaluate communications link budget.

PARAMETER DESCRIPTION
arch

Architecture configuration

TYPE: Architecture

scenario

Communications link scenario

TYPE: CommsLinkScenario

context

Additional context, may include antenna metrics: - g_peak_db: Antenna gain (uses this if provided) - scan_loss_db: Scan loss (uses this if provided)

TYPE: dict[str, Any]

RETURNS DESCRIPTION
MetricsDict

Dictionary with link budget metrics: - tx_power_total_dbw: Total transmit power (dBW) - tx_power_per_elem_dbw: TX power per element (dBW) - g_tx_db: Transmit antenna gain (dB) - eirp_dbw: Effective Isotropic Radiated Power (dBW) - path_loss_db: Total path loss (dB) - g_rx_db: Receive antenna gain (dB) - rx_power_dbw: Received power (dBW) - noise_power_dbw: Noise power (dBW) - snr_rx_db: Received SNR (dB) - link_margin_db: Link margin (dB)

Source code in src/phased_array_systems/models/comms/link_budget.py
def evaluate(
    self,
    arch: Architecture,
    scenario: CommsLinkScenario,
    context: dict[str, Any],
) -> MetricsDict:
    """Evaluate communications link budget.

    Args:
        arch: Architecture configuration
        scenario: Communications link scenario
        context: Additional context, may include antenna metrics:
            - g_peak_db: Antenna gain (uses this if provided)
            - scan_loss_db: Scan loss (uses this if provided)

    Returns:
        Dictionary with link budget metrics:
            - tx_power_total_dbw: Total transmit power (dBW)
            - tx_power_per_elem_dbw: TX power per element (dBW)
            - g_tx_db: Transmit antenna gain (dB)
            - eirp_dbw: Effective Isotropic Radiated Power (dBW)
            - path_loss_db: Total path loss (dB)
            - g_rx_db: Receive antenna gain (dB)
            - rx_power_dbw: Received power (dBW)
            - noise_power_dbw: Noise power (dBW)
            - snr_rx_db: Received SNR (dB)
            - link_margin_db: Link margin (dB)
    """
    # Get transmit power
    n_elements = arch.array.n_elements
    tx_power_per_elem_w = arch.rf.tx_power_w_per_elem
    tx_power_total_w = tx_power_per_elem_w * n_elements
    tx_power_total_dbw = W_TO_DBW(tx_power_total_w)
    tx_power_per_elem_dbw = W_TO_DBW(tx_power_per_elem_w)

    # Get antenna gain from context or compute approximate
    if "g_peak_db" in context:
        g_tx_db = context["g_peak_db"]
    else:
        # Approximate gain for uniform array
        # G = 4*pi*A/lambda^2 ≈ pi * nx * dx * ny * dy * 4
        aperture_area_lambda_sq = (
            arch.array.nx * arch.array.dx_lambda *
            arch.array.ny * arch.array.dy_lambda
        )
        g_tx_linear = 4 * math.pi * aperture_area_lambda_sq
        g_tx_db = 10 * math.log10(g_tx_linear)

        # Apply scan loss if provided
        if "scan_loss_db" in context:
            g_tx_db -= context["scan_loss_db"]

    # Transmit losses (feed network + system)
    tx_loss_db = arch.rf.feed_loss_db + arch.rf.system_loss_db

    # EIRP
    eirp_dbw = tx_power_total_dbw + g_tx_db - tx_loss_db

    # Path loss
    if scenario.path_loss_model == "fspl":
        path_loss_db = compute_fspl(scenario.freq_hz, scenario.range_m)
    else:
        raise ValueError(f"Unknown path loss model: {scenario.path_loss_model}")

    # Add extra losses
    total_path_loss_db = path_loss_db + scenario.total_extra_loss_db

    # Receive antenna gain (isotropic if not specified)
    g_rx_db = scenario.rx_antenna_gain_db if scenario.rx_antenna_gain_db is not None else 0.0

    # Received power
    rx_power_dbw = eirp_dbw - total_path_loss_db + g_rx_db

    # Noise power: N = k*T*B
    noise_power_w = K_B * scenario.rx_noise_temp_k * scenario.bandwidth_hz
    noise_power_dbw = W_TO_DBW(noise_power_w)

    # Add receiver noise figure
    noise_power_dbw += arch.rf.noise_figure_db

    # SNR
    snr_rx_db = rx_power_dbw - noise_power_dbw

    # Link margin
    link_margin_db = snr_rx_db - scenario.required_snr_db

    return {
        "tx_power_total_dbw": tx_power_total_dbw,
        "tx_power_per_elem_dbw": tx_power_per_elem_dbw,
        "g_tx_db": g_tx_db,
        "eirp_dbw": eirp_dbw,
        "path_loss_db": total_path_loss_db,
        "fspl_db": path_loss_db,
        "g_rx_db": g_rx_db,
        "rx_power_dbw": rx_power_dbw,
        "noise_power_dbw": noise_power_dbw,
        "snr_rx_db": snr_rx_db,
        "link_margin_db": link_margin_db,
        "required_snr_db": scenario.required_snr_db,
    }

Functions

compute_link_margin(eirp_dbw: float, path_loss_db: float, g_rx_db: float, noise_temp_k: float, bandwidth_hz: float, noise_figure_db: float, required_snr_db: float) -> dict[str, float]

Standalone link margin calculation.

Convenience function for quick link budget calculations without full Architecture/Scenario objects.

PARAMETER DESCRIPTION
eirp_dbw

Effective Isotropic Radiated Power (dBW)

TYPE: float

path_loss_db

Total path loss (dB)

TYPE: float

g_rx_db

Receive antenna gain (dB)

TYPE: float

noise_temp_k

System noise temperature (K)

TYPE: float

bandwidth_hz

Signal bandwidth (Hz)

TYPE: float

noise_figure_db

Receiver noise figure (dB)

TYPE: float

required_snr_db

Required SNR for demodulation (dB)

TYPE: float

RETURNS DESCRIPTION
dict[str, float]

Dictionary with rx_power_dbw, noise_power_dbw, snr_db, margin_db

Source code in src/phased_array_systems/models/comms/link_budget.py
def compute_link_margin(
    eirp_dbw: float,
    path_loss_db: float,
    g_rx_db: float,
    noise_temp_k: float,
    bandwidth_hz: float,
    noise_figure_db: float,
    required_snr_db: float,
) -> dict[str, float]:
    """Standalone link margin calculation.

    Convenience function for quick link budget calculations
    without full Architecture/Scenario objects.

    Args:
        eirp_dbw: Effective Isotropic Radiated Power (dBW)
        path_loss_db: Total path loss (dB)
        g_rx_db: Receive antenna gain (dB)
        noise_temp_k: System noise temperature (K)
        bandwidth_hz: Signal bandwidth (Hz)
        noise_figure_db: Receiver noise figure (dB)
        required_snr_db: Required SNR for demodulation (dB)

    Returns:
        Dictionary with rx_power_dbw, noise_power_dbw, snr_db, margin_db
    """
    rx_power_dbw = eirp_dbw - path_loss_db + g_rx_db
    noise_power_w = K_B * noise_temp_k * bandwidth_hz
    noise_power_dbw = W_TO_DBW(noise_power_w) + noise_figure_db
    snr_db = rx_power_dbw - noise_power_dbw
    margin_db = snr_db - required_snr_db

    return {
        "rx_power_dbw": rx_power_dbw,
        "noise_power_dbw": noise_power_dbw,
        "snr_db": snr_db,
        "margin_db": margin_db,
    }

compute_fspl

compute_fspl(freq_hz: float, range_m: float) -> float

Compute Free Space Path Loss (FSPL).

FSPL = 20log10(4pidf/c) = 20log10(d) + 20log10(f) + 20log10(4pi/c) = 20log10(d) + 20log10(f) - 147.55 (with d in m, f in Hz)

PARAMETER DESCRIPTION
freq_hz

Frequency in Hz

TYPE: float

range_m

Range/distance in meters

TYPE: float

RETURNS DESCRIPTION
float

Free space path loss in dB (positive value)

Source code in src/phased_array_systems/models/comms/propagation.py
def compute_fspl(freq_hz: float, range_m: float) -> float:
    """Compute Free Space Path Loss (FSPL).

    FSPL = 20*log10(4*pi*d*f/c)
         = 20*log10(d) + 20*log10(f) + 20*log10(4*pi/c)
         = 20*log10(d) + 20*log10(f) - 147.55 (with d in m, f in Hz)

    Args:
        freq_hz: Frequency in Hz
        range_m: Range/distance in meters

    Returns:
        Free space path loss in dB (positive value)
    """
    if freq_hz <= 0:
        raise ValueError("Frequency must be positive")
    if range_m <= 0:
        raise ValueError("Range must be positive")

    wavelength = C / freq_hz
    fspl_linear = (4 * math.pi * range_m / wavelength) ** 2
    return 10 * math.log10(fspl_linear)

Output Metrics

Metric Units Description
tx_power_total_dbw dBW Total TX power
tx_power_per_elem_dbw dBW TX power per element
g_tx_db dB Transmit antenna gain
eirp_dbw dBW Effective isotropic radiated power
path_loss_db dB Total path loss
fspl_db dB Free space path loss only
g_rx_db dB Receive antenna gain
rx_power_dbw dBW Received signal power
noise_power_dbw dBW Receiver noise power
snr_rx_db dB Received SNR
link_margin_db dB Margin above required SNR
required_snr_db dB Required SNR

Usage Examples

Using CommsLinkModel

from phased_array_systems.models.comms.link_budget import CommsLinkModel

model = CommsLinkModel()

# Without pre-computed antenna gain
metrics = model.evaluate(arch, scenario, context={})

# With pre-computed antenna gain
context = {
    "g_peak_db": 28.0,
    "scan_loss_db": 2.5,
}
metrics = model.evaluate(arch, scenario, context)
from phased_array_systems.models.comms.link_budget import compute_link_margin

result = compute_link_margin(
    eirp_dbw=50.0,
    path_loss_db=160.0,
    g_rx_db=30.0,
    noise_temp_k=290.0,
    bandwidth_hz=10e6,
    noise_figure_db=3.0,
    required_snr_db=10.0,
)

print(f"SNR: {result['snr_db']:.1f} dB")
print(f"Margin: {result['margin_db']:.1f} dB")

Free Space Path Loss

from phased_array_systems.models.comms.propagation import compute_fspl

# At 10 GHz, 100 km
loss = compute_fspl(freq_hz=10e9, range_m=100e3)
print(f"FSPL: {loss:.1f} dB")  # ~152.4 dB

EIRP

\[ EIRP = P_{tx} + G_{tx} - L_{tx} \]

Path Loss

\[ L_{path} = L_{FSPL} + L_{atm} + L_{rain} + L_{pol} \]

Free Space Path Loss

\[ L_{FSPL} = 20 \log_{10}\left(\frac{4\pi d f}{c}\right) \]

Received Power

\[ P_{rx} = EIRP - L_{path} + G_{rx} \]

Noise Power

\[ N = 10 \log_{10}(kTB) + NF \]

SNR and Margin

\[ SNR = P_{rx} - N $$ $$ Margin = SNR - SNR_{required} \]

See Also