Skip to content
RFrftools.io
Satellite CommunicationsApril 30, 20268 min read

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

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.

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.

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

  1. Units matter. f=20.0 without * itur.u.GHz silently assumes Hz. ITU-Rpy validates dimensionally.
  2. Availability is outage %, not uptime %. p=0.01 means 99.99% availability (0.01% of time the attenuation exceeds that value).
  3. 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).
  4. Cloud attenuation needs antenna diameter. Larger antennas have tighter beams and see less cloud attenuation in dB.
  5. Scintillation is a separate function. Use itur.models.itu618.scintillation_attenuation() — it's not included in the default atmospheric_attenuation_slant_path call.

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 CommITU-Rpy equivalent
Rain attenuation modelitur.models.itu838.specific_attenuation()
Gaseous absorptionitur.models.itu676.gaseous_attenuation_slant_path()
Cloud attenuationitur.models.itu840.columnar_content_reduced_liquid()
Scintillationitur.models.itu618.scintillation_attenuation()
Total attenuationitur.atmospheric_attenuation_slant_path()
For anything short of a full-blown constellation simulation, this is what you want. See also Migrating from STK Cloud for the complete tool-replacement map.

Further reading

Related Articles