opensatcom: an open toolkit for satcom link budgets and trade studies
A satellite link has to deliver enough signal over enough noise to close a margin, after the antennas, the RF chain, the geometry, and the atmosphere have each taken their cut. The link budget is the bookkeeping that adds those terms up, and it is where most satcom system decisions are won or lost.
I built opensatcom to make that bookkeeping fast,
reproducible, and ready for early-stage engineering trade studies. It maps an antenna, an RF chain, a
propagation model, and a mission time-series to link margin and estimated throughput, with typed inputs
and outputs you can sweep. It uses simplified, ITU-R-inspired engineering models for early,
system-level work. It does not replace STK, a full ITU-R implementation, or hardware test. (The project page has a shorter
overview.)
The link budget in one call
A snapshot link budget is one function call. You describe two terminals, a scenario, the antennas, the RF chain, and a propagation model, and the engine returns the chain of intermediate quantities, not just the answer.
from opensatcom.core.models import (
Terminal, Scenario, LinkInputs, RFChainModel, PropagationConditions,
)
from opensatcom.antenna.parametric import ParametricAntenna
from opensatcom.propagation import (
CompositePropagation, FreeSpacePropagation, GaseousAbsorptionP676, RainAttenuationP618,
)
from opensatcom.link.engine import DefaultLinkEngine
from opensatcom.geometry.slant import slant_range_m
sat = Terminal("GEO-Sat", 0.0, 0.0, 35_786_000.0)
gnd = Terminal("Ground", 38.9, -77.0, 0.0, system_noise_temp_k=290.0)
link = LinkInputs(
tx_terminal=sat, rx_terminal=gnd,
scenario=Scenario(name="Ka-DL", direction="downlink", freq_hz=20e9,
bandwidth_hz=36e6, polarization="RHCP",
required_metric="ebn0_db", required_value=5.0),
tx_antenna=ParametricAntenna(gain_dbi=38.0),
rx_antenna=ParametricAntenna(gain_dbi=40.0),
propagation=CompositePropagation([
FreeSpacePropagation(), GaseousAbsorptionP676(), RainAttenuationP618(),
]),
rf_chain=RFChainModel(tx_power_w=100.0, tx_losses_db=1.5, rx_noise_temp_k=120.0),
)
rng = slant_range_m(0.0, 35_786_000.0, 30.0)
result = DefaultLinkEngine().evaluate_snapshot(30.0, 0.0, rng, link, PropagationConditions())
print(f"margin {result.margin_db:.1f} dB, C/N0 {result.cn0_dbhz:.1f} dB-Hz")
The result carries EIRP, G/T, path loss, C/N0, Eb/N0, margin, and throughput, so you can see which term moved when a parameter changes.
Propagation is where the margin goes
Free-space spreading sets the scale, and the atmosphere decides whether a clear-sky design survives weather. opensatcom composes free-space loss with simplified, ITU-R-inspired models: P.676 for gaseous absorption, P.618 for rain, and a scintillation term. Their sum climbs with frequency, which is one reason Ka-band links need more fade margin than C- or Ku-band links.

Composite atmospheric attenuation at 30 degrees elevation. The gaseous term peaks at the water-vapor line near 22 GHz, and rain climbs through Ka-band. These are the package’s simplified, ITU-R-inspired models, so read the trend rather than the absolute decibels; a full P.618 run gives larger rain figures.
Geometry and elevation
The same link looks different across the sky. At a low elevation angle the signal travels a longer slant path through more atmosphere, so margin falls, and rain pushes it down further. Sweeping the elevation through the engine shows where a design stops closing.

Margin against elevation for the 20 GHz downlink above. Clear sky closes down to about 8 degrees; with 25 mm/h rain the same link needs roughly 9 degrees or more. The shaded band is the rain penalty.
From C/N0 to bits
A link budget lands on a ratio like C/N0 or Eb/N0, and turning that into throughput is the modem’s job. opensatcom ships the 28 DVB-S2 ModCods with analytic BER curves and an adaptive coding and modulation policy, so a rising C/N0 steps the link up a staircase of spectral efficiency toward the Shannon limit.

The 28 DVB-S2 ModCods as a staircase of spectral efficiency against required Eb/N0 (at a block error rate of 1e-5), with the Shannon bound for reference. Each extra decibel of margin buys a step up in bits per hertz.
Beyond one link
A single snapshot is the building block. The rest of opensatcom uses it at scale: a multi-beam payload
layer builds beam sets and computes signal-to-interference-plus-noise maps with co-channel
interference; a mission layer runs single-satellite passes, multi-satellite handover, and
network-level traffic; and a trade-study layer drives designs of experiments (Latin hypercube,
factorial, random), batch evaluation, and Pareto extraction. There is a command-line interface
(opensatcom run, mission, beammap, doe, batch, pareto) and HTML reports with the charts
embedded. Across all of it, opensatcom is meant as a transparent, reproducible sandbox for early
link-budget reasoning, a step before validated tools, full ITU-R implementations, or measured hardware.
Try it
pip install opensatcom
opensatcom run --help
The repo, the documentation, and the PyPI package have the notebooks, the API reference, and the theory notes.
opensatcom is early-stage open-source software (MIT). The propagation models are simplified, ITU-R-inspired implementations, and the figures come from one geometry to show the method rather than a validated link. The references are public.
This is an independent project I build on my own time using public sources and open tooling. The views are my own and do not represent any current or former employer.