Scripting Satellite Link Budgets with ITU-Rpy (Python Examples)
STK-free link-budget automation: sweep frequency, rain availability, and elevation in pure Python using the ITU-Rpy reference implementation of P.618/P.676/P.840. Companion to the rftools Satellite Link Budget Analyzer.
Contents
- Why script your link budgets
- Install
- Use case 1: Frequency sweep for a Ka-band downlink
- Use case 2: Availability sweep for a rain-fade trade study
- Use case 3: Elevation mask — where does the link close at low elevation?
- Feeding the output back into the rftools Analyzer
- Common gotchas
- Replacing STK Cloud's Comm module
- Further reading
Why script your link budgets
The rftools Satellite Link Budget Analyzer is built for interactive scenario design. When you need to sweep a parameter across hundreds of points — for trade studies, frequency-plan optimization, or CI/CD integration into your mission-design repo — you want Python, not a browser.
ITU-Rpy is the open-source reference implementation of the ITU-R Recommendations for Earth-space propagation. Our backend aligns with it. This post shows three scripted use cases you can lift straight into a Jupyter notebook.Install
pip install itur numpy matplotlib
ITU-Rpy pulls its own data tables (rain rate maps, refractive index climatologies) from the ITU repository. First import may take ~5 seconds.
Use case 1: Frequency sweep for a Ka-band downlink
You're designing a GEO broadcast downlink and want to know how specific rain attenuation varies from 20 to 40 GHz at 99.99% availability.
import itur
import itur.models as m
import numpy as np
import matplotlib.pyplot as plt
# Ground station: Atlanta, GA
lat, lon = 33.75, -84.39
elevation_deg = 45.0
availability_pct = 0.01 # 0.01% outage → 99.99% availability
freqs = np.linspace(20, 40, 21) # GHz
attenuations = []
for f in freqs:
a = itur.atmospheric_attenuation_slant_path(
lat=lat, lon=lon,
f=f * itur.u.GHz,
el=elevation_deg,
p=availability_pct,
D=1.2 * itur.u.m, # antenna diameter (affects cloud attenuation)
hs=0.3 * itur.u.km,
)
attenuations.append(float(a.value))
plt.plot(freqs, attenuations)
plt.xlabel('Frequency (GHz)')
plt.ylabel('Total atmospheric attenuation (dB)')
plt.title(f'Ka-band slant path, {availability_pct}% outage, Atlanta 45° elevation')
plt.grid(True)
plt.show()
At Ka-band over the Atlanta climate zone (ITU-R rain zone K), 20 GHz sees about 2-3 dB of total atmospheric attenuation at 99.99%. By 40 GHz that climbs to 15-20 dB — which is why Ka-band broadcasters typically allocate 5-6 dB of uplink-power-control margin beyond nominal.
Use case 2: Availability sweep for a rain-fade trade study
You need to decide between 99.9% (80 minutes outage/year), 99.99% (52 minutes outage/year), and 99.999% (5 minutes outage/year) for a maritime VSAT. How much extra fade margin does each tier cost?
availabilities = [1.0, 0.1, 0.01, 0.001] # 99% through 99.999%
freq = 14.5 # Ku-band uplink
lat, lon = 42.0, -3.0 # Bay of Biscay, typical rain zone M
print(f'{"Availability":14s} {"Outage/yr":12s} {"Total Atten":12s}')
print('-' * 40)
for p in availabilities:
a = itur.atmospheric_attenuation_slant_path(
lat=lat, lon=lon,
f=freq * itur.u.GHz,
el=30.0,
p=p,
)
outage_min = p / 100 * 365.25 * 24 * 60
pct = 100 - p
print(f'{pct:9.3f} % {outage_min:9.1f} min {float(a.value):8.2f} dB')
Typical output:
Availability Outage/yr Total Atten
----------------------------------------
99.000 % 5259.6 min 1.48 dB
99.900 % 525.9 min 3.21 dB
99.990 % 52.6 min 6.58 dB
99.999 % 5.3 min 11.42 dB
Going from 99.99% to 99.999% costs about 5 dB at Ku-band — which for most maritime use cases is not worth the hardware budget. The trade-study table makes the conversation with stakeholders easy.
Use case 3: Elevation mask — where does the link close at low elevation?
For a LEO constellation with a fixed ground terminal, low-elevation passes have much higher atmospheric attenuation. Plot total attenuation vs elevation:
elevations = np.arange(5, 85, 5)
attens = []
for el in elevations:
a = itur.atmospheric_attenuation_slant_path(
lat=51.5, lon=-0.1, # London
f=20.0 * itur.u.GHz,
el=el,
p=0.1,
)
attens.append(float(a.value))
plt.plot(elevations, attens)
plt.xlabel('Elevation angle (deg)')
plt.ylabel('Total attenuation (dB) at 20 GHz, 99.9%')
plt.title('Atmospheric + rain attenuation vs elevation — London')
plt.grid(True)
plt.show()
At 5° elevation you see 8-10 dB more than at 45° — which is why LEO Ka-band terminals use a minimum elevation mask of 15-20° and deprioritize low-elevation contacts.
Feeding the output back into the rftools Analyzer
Once you have a scripted attenuation number, plug it into the Satellite Link Budget Analyzer as rainFade or atmosphericLoss, run the Monte Carlo, and copy the scenario URL for sharing. Our backend uses the same ITU-R models so results match within floating-point precision.
For automated regression testing, our POST /api/py/v1/calculate endpoint accepts the rf-link-budget calculator with all inputs — perfect for CI pipelines that verify link-budget compliance on every commit.
Common gotchas
- Units matter.
f=20.0without* itur.u.GHzsilently assumes Hz. ITU-Rpy validates dimensionally. - Availability is outage %, not uptime %.
p=0.01means 99.99% availability (0.01% of time the attenuation exceeds that value). - Rain zone is derived from lat/lon. You cannot override it — if you're testing for Zone P (worst-case tropical) you must pass a lat/lon in that zone, e.g.,
lat=1.3, lon=103.8(Singapore). - Cloud attenuation needs antenna diameter. Larger antennas have tighter beams and see less cloud attenuation in dB.
- Scintillation is a separate function. Use
itur.models.itu618.scintillation_attenuation()— it's not included in the defaultatmospheric_attenuation_slant_pathcall.
Replacing STK Cloud's Comm module
STK Cloud's Comm/Radar suite wrapped these ITU-R calculations in a UI. For scripting, ITU-Rpy is the reference implementation they would have called. Migration:
| STK Cloud Comm | ITU-Rpy equivalent |
|---|---|
| Rain attenuation model | itur.models.itu838.specific_attenuation() |
| Gaseous absorption | itur.models.itu676.gaseous_attenuation_slant_path() |
| Cloud attenuation | itur.models.itu840.columnar_content_reduced_liquid() |
| Scintillation | itur.models.itu618.scintillation_attenuation() |
| Total attenuation | itur.atmospheric_attenuation_slant_path() |
Further reading
Related Articles
Building a Ground-Station Pass Schedule with Skyfield
Ansys STK sunset-ready: compute SGP4 pass predictions for your amateur-radio or cubesat ground station using Skyfield. Full Python walkthrough with TLE fetch, elevation masks, Doppler, and iCalendar export.
Apr 30, 2026
Satellite CommunicationsSizing a 9600-baud UHF Downlink for a 3U CubeSat: Full Walkthrough
End-to-end link budget for an amateur-band 3U cubesat: EIRP, ground-station G/T, ITU-R propagation losses, and Monte Carlo availability. Uses the Amateur CubeSat preset.
Apr 29, 2026
Satellite CommunicationsMigrating from STK Cloud: Free Alternatives for Link Budget and Orbit Analysis
Ansys is sunsetting STK Cloud in March 2026. Here are the free open-source replacements for the two things it did best — ITU-R link budgets and orbital pass prediction.
Apr 29, 2026