Skip to content

Payload

Multi-beam payload modeling — beam definitions, beam sets, capacity maps, and interference analysis.

Beam

Beam dataclass

Beam(beam_id, az_deg, el_deg, tx_power_w, antenna)

A single satellite beam with its own antenna pattern.

Parameters:

Name Type Description Default
beam_id str

Unique identifier for this beam.

required
az_deg float

Boresight azimuth in degrees.

required
el_deg float

Boresight elevation in degrees.

required
tx_power_w float

Transmit power allocated to this beam in watts.

required
antenna AntennaModel

Antenna model describing the beam's radiation pattern.

required

gain_toward_dbi

gain_toward_dbi(az_deg, el_deg, f_hz)

Evaluate beam antenna gain toward a specific direction.

Parameters:

Name Type Description Default
az_deg target azimuth (deg)
required
el_deg target elevation (deg)
required
f_hz frequency(Hz)
required

Returns:

Type Description
Gain in dBi toward the specified direction.
Source code in src/opensatcom/payload/beam.py
def gain_toward_dbi(self, az_deg: float, el_deg: float, f_hz: float) -> float:
    """Evaluate beam antenna gain toward a specific direction.

    Parameters
    ----------
    az_deg : target azimuth (deg)
    el_deg : target elevation (deg)
    f_hz : frequency (Hz)

    Returns
    -------
    Gain in dBi toward the specified direction.
    """
    theta = np.array([az_deg])
    phi = np.array([el_deg])
    return float(self.antenna.gain_dbi(theta, phi, f_hz)[0])

eirp_toward_dbw

eirp_toward_dbw(az_deg, el_deg, f_hz)

EIRP toward a specific direction.

Parameters:

Name Type Description Default
az_deg float

Target azimuth in degrees.

required
el_deg float

Target elevation in degrees.

required
f_hz float

Frequency in Hz.

required

Returns:

Type Description
float

EIRP in dBW toward the specified direction.

Source code in src/opensatcom/payload/beam.py
def eirp_toward_dbw(self, az_deg: float, el_deg: float, f_hz: float) -> float:
    """EIRP toward a specific direction.

    Parameters
    ----------
    az_deg : float
        Target azimuth in degrees.
    el_deg : float
        Target elevation in degrees.
    f_hz : float
        Frequency in Hz.

    Returns
    -------
    float
        EIRP in dBW toward the specified direction.
    """
    gain = self.gain_toward_dbi(az_deg, el_deg, f_hz)
    from opensatcom.core.units import w_to_dbw

    return w_to_dbw(self.tx_power_w) + gain

BeamSet

BeamSet

BeamSet(beams, scenario, propagation, rf_chain)

A set of satellite beams sharing common scenario, propagation, and RF chain.

Parameters:

Name Type Description Default
beams list[Beam]

List of Beam objects forming the multi-beam payload.

required
scenario Scenario

Link scenario defining frequency, bandwidth, and requirements.

required
propagation PropagationModel

Propagation model used for path loss computation.

required
rf_chain RFChainModel

Shared RF chain parameters (e.g., noise temperature).

required
Source code in src/opensatcom/payload/beamset.py
def __init__(
    self,
    beams: list[Beam],
    scenario: Scenario,
    propagation: PropagationModel,
    rf_chain: RFChainModel,
) -> None:
    self._beams = list(beams)
    self._beam_map = {b.beam_id: b for b in self._beams}
    self.scenario = scenario
    self.propagation = propagation
    self.rf_chain = rf_chain

beam_ids property

beam_ids

List of beam IDs in insertion order.

get_beam

get_beam(beam_id)

Look up a beam by its ID.

Parameters:

Name Type Description Default
beam_id str

Unique identifier of the beam to retrieve.

required

Returns:

Type Description
Beam

The beam matching the given ID.

Source code in src/opensatcom/payload/beamset.py
def get_beam(self, beam_id: str) -> Beam:
    """Look up a beam by its ID.

    Parameters
    ----------
    beam_id : str
        Unique identifier of the beam to retrieve.

    Returns
    -------
    Beam
        The beam matching the given ID.
    """
    return self._beam_map[beam_id]

Capacity Map

capacity

Capacity map computation — evaluates interference across an az/el grid.

compute_beam_map

compute_beam_map(beamset, grid_az_deg, grid_el_deg, rx_antenna, rx_terminal, range_m, cond, beam_selection='max_gain')

Compute a beam map (capacity/interference map) over an az/el grid.

Parameters:

Name Type Description Default
beamset multi-beam payload
required
grid_az_deg 1-D array of azimuth values for the grid
required
grid_el_deg 1-D array of elevation values for the grid
required
rx_antenna victim receive antenna
required
rx_terminal victim terminal (for noise temp)
required
range_m slant range to the victim
required
cond propagation conditions
required
beam_selection 'max_gain'(default) or 'nearest'
'max_gain'

Returns:

Type Description
BeamMap

Beam map with one BeamMapPoint per (az, el) grid combination.

Source code in src/opensatcom/payload/capacity.py
def compute_beam_map(
    beamset: BeamSet,
    grid_az_deg: np.ndarray,
    grid_el_deg: np.ndarray,
    rx_antenna: AntennaModel,
    rx_terminal: Terminal,
    range_m: float,
    cond: PropagationConditions,
    beam_selection: str = "max_gain",
) -> BeamMap:
    """Compute a beam map (capacity/interference map) over an az/el grid.

    Parameters
    ----------
    beamset : multi-beam payload
    grid_az_deg : 1-D array of azimuth values for the grid
    grid_el_deg : 1-D array of elevation values for the grid
    rx_antenna : victim receive antenna
    rx_terminal : victim terminal (for noise temp)
    range_m : slant range to the victim
    cond : propagation conditions
    beam_selection : "max_gain" (default) or "nearest"

    Returns
    -------
    BeamMap
        Beam map with one ``BeamMapPoint`` per (az, el) grid combination.
    """
    model = SimpleInterferenceModel()
    f_hz = beamset.scenario.freq_hz
    points: list[BeamMapPoint] = []

    for az in grid_az_deg:
        for el in grid_el_deg:
            az_f = float(az)
            el_f = float(el)

            serving_id = _select_serving_beam(
                beamset, az_f, el_f, f_hz, beam_selection
            )

            result = model.evaluate(
                beamset, serving_id, az_f, el_f,
                range_m, rx_antenna, rx_terminal, cond,
            )

            points.append(BeamMapPoint(az_f, el_f, serving_id, result))

    return BeamMap(points)

BeamMap

BeamMapPoint dataclass

BeamMapPoint(az_deg, el_deg, serving_beam_id, result)

A single evaluated point in the beam map grid.

Parameters:

Name Type Description Default
az_deg float

Azimuth of the grid point in degrees.

required
el_deg float

Elevation of the grid point in degrees.

required
serving_beam_id str

Identifier of the beam serving this grid point.

required
result InterferenceResult

Interference evaluation result at this point.

required

BeamMap

BeamMap(points)

Collection of evaluated beam map points with analysis methods.

Parameters:

Name Type Description Default
points list[BeamMapPoint]

List of evaluated beam map points, each holding interference evaluation results for one grid location.

required
Source code in src/opensatcom/payload/beammap.py
def __init__(self, points: list[BeamMapPoint]) -> None:
    self._points = list(points)

sinr_db_mean property

sinr_db_mean

Mean SINR across all points (excluding inf).

sinr_db_min property

sinr_db_min

Minimum SINR across all points.

cnir_db_mean property

cnir_db_mean

Mean C/(N+I) across all points.

margin_db_mean property

margin_db_mean

Mean margin across all points.

throughput_mbps_total property

throughput_mbps_total

Total throughput summed across all points.

to_dataframe

to_dataframe()

Export beam map to a DataFrame for analysis and persistence.

Returns:

Type Description
DataFrame

DataFrame with columns: az_deg, el_deg, serving_beam_id, signal_dbw, interference_dbw, noise_dbw, cnir_db, sinr_db, cn0_dbhz, ebn0_db, margin_db, and throughput_mbps.

Source code in src/opensatcom/payload/beammap.py
def to_dataframe(self) -> pd.DataFrame:
    """Export beam map to a DataFrame for analysis and persistence.

    Returns
    -------
    pd.DataFrame
        DataFrame with columns: ``az_deg``, ``el_deg``,
        ``serving_beam_id``, ``signal_dbw``, ``interference_dbw``,
        ``noise_dbw``, ``cnir_db``, ``sinr_db``, ``cn0_dbhz``,
        ``ebn0_db``, ``margin_db``, and ``throughput_mbps``.
    """
    records = []
    for p in self._points:
        r = p.result
        records.append({
            "az_deg": p.az_deg,
            "el_deg": p.el_deg,
            "serving_beam_id": p.serving_beam_id,
            "signal_dbw": r.signal_dbw,
            "interference_dbw": r.interference_dbw,
            "noise_dbw": r.noise_dbw,
            "cnir_db": r.cnir_db,
            "sinr_db": r.sinr_db,
            "cn0_dbhz": r.cn0_dbhz,
            "ebn0_db": r.ebn0_db,
            "margin_db": r.margin_db,
            "throughput_mbps": r.throughput_mbps,
        })
    return pd.DataFrame(records)

per_beam_summary

per_beam_summary()

Per-beam summary statistics.

Returns:

Type Description
dict[str, dict[str, float]]

Mapping of beam_id to a dict containing points_served, sinr_db_mean, and margin_db_mean.

Source code in src/opensatcom/payload/beammap.py
def per_beam_summary(self) -> dict[str, dict[str, float]]:
    """Per-beam summary statistics.

    Returns
    -------
    dict[str, dict[str, float]]
        Mapping of ``beam_id`` to a dict containing
        ``points_served``, ``sinr_db_mean``, and ``margin_db_mean``.
    """
    from collections import defaultdict

    beam_points: dict[str, list[BeamMapPoint]] = defaultdict(list)
    for p in self._points:
        beam_points[p.serving_beam_id].append(p)

    summary = {}
    for bid, pts in beam_points.items():
        sinr_vals = [p.result.sinr_db for p in pts if np.isfinite(p.result.sinr_db)]
        summary[bid] = {
            "points_served": float(len(pts)),
            "sinr_db_mean": float(np.mean(sinr_vals)) if sinr_vals else 0.0,
            "margin_db_mean": float(np.mean([p.result.margin_db for p in pts])),
        }
    return summary

Interference Model

InterferenceResult dataclass

InterferenceResult(serving_beam_id, signal_dbw, interference_dbw, noise_dbw, cnir_db, sinr_db, cn0_dbhz, ebn0_db, margin_db, throughput_mbps=None)

Result of an interference evaluation at a single point.

All power values are in dB domain. Interference and noise are summed in linear domain then converted.

Parameters:

Name Type Description Default
serving_beam_id str

Identifier of the beam serving this point.

required
signal_dbw float

Received signal power from the serving beam in dBW.

required
interference_dbw float

Total co-channel interference power from non-serving beams in dBW.

required
noise_dbw float

Thermal noise power in dBW.

required
cnir_db float

Carrier-to-noise-plus-interference ratio C/(N+I) in dB.

required
sinr_db float

Signal-to-interference ratio C/I in dB (inf when no interference).

required
cn0_dbhz float

Carrier-to-noise-density ratio C/N0 in dB-Hz.

required
ebn0_db float

Energy-per-bit to noise-density ratio Eb/N0 in dB.

required
margin_db float

Link margin relative to the scenario requirement in dB.

required
throughput_mbps float or None

Achievable throughput in Mbps, or None if not computed.

None

SimpleInterferenceModel

Evaluate signal, interference, and noise for a multi-beam payload.

For a victim at a given direction and range: 1. Signal (C) = serving beam's EIRP toward victim / path_loss * rx_gain 2. Interference (I) = sum of non-serving beams' EIRP / path_loss * rx_gain 3. Noise (N) = kB * T_sys * bandwidth 4. SINR = C / I (linear, then to dB) 5. C/(N+I) = C / (N + I)

Path loss is the same for all beams (same satellite, same range to victim).

evaluate

evaluate(beamset, serving_beam_id, victim_az_deg, victim_el_deg, range_m, rx_antenna, rx_terminal, cond)

Evaluate interference at a single victim location.

Parameters:

Name Type Description Default
beamset BeamSet

The multi-beam payload containing all beams.

required
serving_beam_id str

Identifier of the beam serving this victim.

required
victim_az_deg float

Azimuth direction to the victim from the satellite in degrees.

required
victim_el_deg float

Elevation direction to the victim from the satellite in degrees.

required
range_m float

Slant range to the victim in meters.

required
rx_antenna AntennaModel

Victim's receive antenna model.

required
rx_terminal Terminal

Victim terminal (provides system noise temperature).

required
cond PropagationConditions

Propagation conditions for path loss evaluation.

required

Returns:

Type Description
InterferenceResult

Interference evaluation result containing signal, interference, noise powers, CNIR, SINR, C/N0, Eb/N0, and margin.

Source code in src/opensatcom/payload/interference.py
def evaluate(
    self,
    beamset: BeamSet,
    serving_beam_id: str,
    victim_az_deg: float,
    victim_el_deg: float,
    range_m: float,
    rx_antenna: AntennaModel,
    rx_terminal: Terminal,
    cond: PropagationConditions,
) -> InterferenceResult:
    """Evaluate interference at a single victim location.

    Parameters
    ----------
    beamset : BeamSet
        The multi-beam payload containing all beams.
    serving_beam_id : str
        Identifier of the beam serving this victim.
    victim_az_deg : float
        Azimuth direction to the victim from the satellite in degrees.
    victim_el_deg : float
        Elevation direction to the victim from the satellite in degrees.
    range_m : float
        Slant range to the victim in meters.
    rx_antenna : AntennaModel
        Victim's receive antenna model.
    rx_terminal : Terminal
        Victim terminal (provides system noise temperature).
    cond : PropagationConditions
        Propagation conditions for path loss evaluation.

    Returns
    -------
    InterferenceResult
        Interference evaluation result containing signal, interference,
        noise powers, CNIR, SINR, C/N0, Eb/N0, and margin.
    """
    sc = beamset.scenario
    rf = beamset.rf_chain
    serving_beam = beamset.get_beam(serving_beam_id)

    # Path loss (same for all beams — same satellite)
    path_loss_db = beamset.propagation.total_path_loss_db(
        sc.freq_hz, victim_el_deg, range_m, cond
    )
    path_loss_lin = db10_to_lin(path_loss_db)

    # RX antenna gain toward satellite direction
    import numpy as np

    rx_gain_dbi = float(
        rx_antenna.gain_dbi(
            np.array([victim_az_deg]), np.array([victim_el_deg]), sc.freq_hz
        )[0]
    )
    rx_gain_lin = db10_to_lin(rx_gain_dbi)

    # Signal power from serving beam (watts at receiver)
    serving_eirp_dbw = serving_beam.eirp_toward_dbw(
        victim_az_deg, victim_el_deg, sc.freq_hz
    )
    c_w = db10_to_lin(serving_eirp_dbw) / path_loss_lin * rx_gain_lin

    # Interference from non-serving beams (sum in linear watts)
    i_total_w = 0.0
    for beam in beamset:
        if beam.beam_id == serving_beam_id:
            continue
        beam_eirp_dbw = beam.eirp_toward_dbw(
            victim_az_deg, victim_el_deg, sc.freq_hz
        )
        i_j_w = db10_to_lin(beam_eirp_dbw) / path_loss_lin * rx_gain_lin
        i_total_w += i_j_w

    # Noise power
    tsys_k = (
        rx_terminal.system_noise_temp_k
        if rx_terminal.system_noise_temp_k is not None
        else rf.rx_noise_temp_k
    )
    # N = kB * T_sys * bandwidth  (watts)
    k_b_lin = db10_to_lin(BOLTZMANN_DBW_PER_K_HZ)  # W/(K·Hz)
    n_w = k_b_lin * tsys_k * sc.bandwidth_hz

    # Convert to dB
    signal_dbw = lin_to_db10(c_w)
    interference_dbw = lin_to_db10(i_total_w) if i_total_w > 0 else -math.inf
    noise_dbw = lin_to_db10(n_w)

    # C/(N+I) in dB
    cnir_db = lin_to_db10(c_w / (n_w + i_total_w))

    # SINR in dB (C/I)
    sinr_db = lin_to_db10(c_w / i_total_w) if i_total_w > 0 else math.inf

    # C/N0 = C / (kB * T_sys) in dB-Hz
    cn0_dbhz = lin_to_db10(c_w / (k_b_lin * tsys_k))

    # Eb/N0 = C/N0 - 10*log10(bandwidth)
    ebn0_db = cn0_dbhz - lin_to_db10(sc.bandwidth_hz)

    # Margin relative to scenario requirement
    if sc.required_metric == "ebn0_db":
        margin_db = ebn0_db - sc.required_value
    elif sc.required_metric == "cn0_dbhz":
        margin_db = cn0_dbhz - sc.required_value
    else:
        margin_db = ebn0_db - sc.required_value

    return InterferenceResult(
        serving_beam_id=serving_beam_id,
        signal_dbw=signal_dbw,
        interference_dbw=interference_dbw,
        noise_dbw=noise_dbw,
        cnir_db=cnir_db,
        sinr_db=sinr_db,
        cn0_dbhz=cn0_dbhz,
        ebn0_db=ebn0_db,
        margin_db=margin_db,
    )