1283 lines
50 KiB
Markdown
1283 lines
50 KiB
Markdown
# Microwave Propagation Algorithm — Unified
|
|
|
|
## Overview
|
|
|
|
Propagation scoring and prediction for microwave amateur radio bands (10-241 GHz), calibrated against 57,488 tropospheric QSOs with distance data, validated against commercial terrestrial links at 11/24/68 GHz, and grounded in ITU-R atmospheric models.
|
|
|
|
The algorithm has two operating regimes:
|
|
1. **Beyond-LOS** — Ham radio paths (50-1000+ km) where atmospheric ducting and refraction are essential. This is the primary use case. Terrain analysis confirms **97.2% of all QSO paths are terrain-blocked** (average diffraction loss 36.2 dB), making atmospheric propagation mechanisms the sole enabler.
|
|
2. **LOS** — Known fixed links or short paths with clear Fresnel clearance where gaseous absorption is the dominant variable.
|
|
|
|
The regime distinction matters because refractivity effects are *inverted* between the two: enhanced refraction extends beyond-LOS range but causes multipath fading on short LOS paths.
|
|
|
|
### Calibration Dataset
|
|
|
|
**QSO data:** 58,282 total QSOs across 13+ bands (ARRL Microwave Contest, 1992-2024). 57,488 with tropospheric distance data after excluding 4 EME contacts (QRA64D/JT4F modes >3,000 km). Enriched with weather from 1,299 ASOS stations (58,398 surface observations), 112 RAOB stations (3,901 soundings), 4,522 HRRR model profiles, 3,675 IEMRE gridded hourly observations, and 58,276 terrain path profiles.
|
|
|
|
**Link data:** 7 commercial links near DFW (Princeton TX area) at 11/24/68 GHz, polled via SNMP at 5-minute intervals. All links use KTKI ASOS for weather correlation. Live polling is active; historical dataset from March 14-29 2026 (18,540 samples) was used for initial algorithm validation.
|
|
|
|
**Terrain analysis:** 58,276 QSO paths profiled — 56,658 BLOCKED (97.2%, avg 36.2 dB diffraction), 1,277 CLEAR (2.2%), 341 FRESNEL_PARTIAL (0.6%). Blocked paths average *longer* distances than clear paths (215 km vs 84 km at 10 GHz) because ducting enables beyond-LOS paths by definition.
|
|
|
|
**Confirmed long-range contacts:**
|
|
- 47 GHz: 116.0 km (Nov 2025), 98.8 km (Jun 2024)
|
|
- 24 GHz: 710.0 km (rainscatter, CW), 542.1 km (Sep 2002, longest tropo)
|
|
- 10 GHz: 2,393 km (longest tropospheric in dataset)
|
|
|
|
**Additional data sources (not yet integrated into scoring):**
|
|
- Solar indices: 9,586 daily values (1998-2026) — SFI, SSN, Ap/Kp. Not relevant for tropospheric microwave propagation but available for future VHF/sporadic-E extension.
|
|
- IEMRE gridded hourly data: weather at QSO endpoint grid points (0.125° resolution) with percentage sky cover, soil temperature, wind components. More granular than nearest-ASOS matching.
|
|
|
|
---
|
|
|
|
## Part 1: Atmospheric Physics
|
|
|
|
### Absolute Humidity
|
|
|
|
The single most important weather variable. Temperature and relative humidity are proxies; absolute humidity (g/m^3) directly determines gaseous absorption.
|
|
|
|
```
|
|
rho = 217 * (RH/100) * e_s / T_kelvin
|
|
|
|
e_s = 6.112 * exp(17.67 * T_c / (T_c + 243.5)) # Magnus formula (hPa)
|
|
```
|
|
|
|
### Surface Refractivity (ITU-R P.453-14)
|
|
|
|
```
|
|
N = 77.6 * P / T + 3.73e5 * e / T^2
|
|
|
|
P: pressure (hPa)
|
|
T: absolute temperature (K)
|
|
e: water vapor pressure (hPa) = 6.112 * exp(17.67 * Td_c / (Td_c + 243.5))
|
|
```
|
|
|
|
N is a compound variable: both dry air (pressure/temperature) and moisture contribute. At 10 GHz, higher N increases beam bending (beneficial for beyond-LOS). At 24+ GHz, higher N usually means more moisture = more absorption (harmful), though the refractivity benefit partially offsets this.
|
|
|
|
### Modified Refractivity (M-units)
|
|
|
|
```
|
|
M = N + 0.157 * (h_agl)
|
|
|
|
h_agl: height above ground level (m)
|
|
```
|
|
|
|
Ducting occurs where dM/dh < 0 (M decreases with height). Duct strength = delta-M across the inversion layer.
|
|
|
|
### Gaseous Absorption (ITU-R P.676-13)
|
|
|
|
Total absorption per km = O2 component (fixed) + H2O component (humidity-dependent):
|
|
|
|
| Band | f (GHz) | O2 (dB/km) | H2O Coeff (dB/km per g/m^3) | Total @ 7.5 g/m^3 | Dominant Constraint |
|
|
|------|---------|------------|---------------------------|-------------------|---------------------|
|
|
| 10G | 10.368 | 0.008 | 0.0005 | 0.012 | Negligible absorption |
|
|
| 24G | 24.192 | 0.015 | 0.012 | 0.105 | 22.235 GHz H2O line |
|
|
| 47G | 47.088 | 0.045 | 0.003 | 0.068 | O2 wing + mild H2O |
|
|
| 68G | 68.040 | 0.90 | 0.007 | 0.95 | 60 GHz O2 band wing |
|
|
| 75G | 76.032 | 0.012 | 0.006 | 0.057 | Window band |
|
|
| 122G | 122.250 | 0.80 | 0.010 | 0.875 | 118.75 GHz O2 wing |
|
|
| 134G | 134.928 | 0.08 | 0.015 | 0.193 | Between O2 118 & H2O 183 |
|
|
| 142G | 142.000 | 0.05 | 0.025 | 0.238 | Approaching H2O 183 |
|
|
| 241G | 241.000 | 0.08 | 0.30 | 2.33 | Between H2O 183 & H2O 325 |
|
|
|
|
The 11 GHz and 24 GHz coefficients are validated by commercial link measurements. The 68 GHz coefficient is directly measured (0.1 dB/km per g/m^3 increase on a 2.8 km path, consistent with ITU-R model when O2 wing contribution is included).
|
|
|
|
### Rain Attenuation (ITU-R P.838-3)
|
|
|
|
gamma_R = k * R^alpha (dB/km), R = rain rate (mm/hr):
|
|
|
|
| Band | k_H | alpha_H | Light 4mm/hr | Moderate 10mm/hr | Heavy 25mm/hr |
|
|
|------|------|------|-------------|-----------------|--------------|
|
|
| 10G | 0.010 | 1.28 | 0.05 | 0.19 | 0.56 |
|
|
| 24G | 0.070 | 1.07 | 0.31 | 0.81 | 2.04 |
|
|
| 47G | 0.187 | 0.93 | 0.68 | 1.58 | 3.69 |
|
|
| 68G | 0.310 | 0.86 | 0.98 | 2.18 | 4.73 |
|
|
| 75G | 0.345 | 0.84 | 1.07 | 2.40 | 5.18 |
|
|
| 122G | 0.498 | 0.77 | 1.32 | 2.93 | 5.91 |
|
|
| 241G | 0.550 | 0.70 | 1.30 | 2.76 | 5.20 |
|
|
|
|
Rain model is NOT validated by measured data (no rain events in link dataset). Coefficients are from ITU-R P.838-3 and interpolation.
|
|
|
|
### Free-Space Path Loss (ITU-R P.525)
|
|
|
|
```
|
|
FSPL = 20 * log10(d_km) + 20 * log10(f_GHz) + 92.45 (dB)
|
|
```
|
|
|
|
### Fresnel Zone Radius
|
|
|
|
```
|
|
r_fresnel = sqrt(lambda * d1 * d2 / (d1 + d2))
|
|
|
|
lambda = 0.3 / f_GHz (meters)
|
|
```
|
|
|
|
### Earth Bulge
|
|
|
|
```
|
|
bulge = (d1 * d2) / (2 * K * 6371000)
|
|
|
|
K: effective earth radius factor (standard = 4/3)
|
|
```
|
|
|
|
### Effective K-Factor from Surface N
|
|
|
|
```
|
|
dN_est = -40 - (N_surface - 315) * 0.25
|
|
K = 1 / (1 + 6371 * dN_est * 1e-6)
|
|
K clamped to [0.5, 5.0]
|
|
```
|
|
|
|
---
|
|
|
|
## Part 2: Key Empirical Findings
|
|
|
|
These findings drive the scoring model's design. Each contradicts or refines assumptions from simpler models.
|
|
|
|
### Finding 1: Humidity Effect Reverses by Frequency
|
|
|
|
The most important discovery. At 10 GHz, more moisture = longer paths (refractivity dominates, absorption negligible). At 24+ GHz, more moisture = shorter paths (absorption dominates).
|
|
|
|
**10 GHz — humidity helps (N=53,013 QSOs):**
|
|
|
|
| Abs. Humidity | Avg Dist | P90 Dist |
|
|
|---------------|----------|----------|
|
|
| 5-8 g/m^3 | 193 km | 342 km |
|
|
| 11-14 g/m^3 | 215 km | 383 km |
|
|
| 17+ g/m^3 | 230 km | 519 km |
|
|
|
|
**24 GHz — humidity hurts (N=3,639 QSOs):**
|
|
|
|
| Abs. Humidity | Avg Dist | P90 Dist |
|
|
|---------------|----------|----------|
|
|
| 5-8 g/m^3 | 115 km | 154 km |
|
|
| 11-14 g/m^3 | 105 km | 174 km |
|
|
| 17+ g/m^3 | 53 km | 103 km |
|
|
|
|
**47 GHz — humidity hurts, less severely (N=689 QSOs):**
|
|
|
|
| Abs. Humidity | Avg Dist | P90 Dist |
|
|
|---------------|----------|----------|
|
|
| 0-5 g/m^3 | 191 km | 234 km |
|
|
| 11-14 g/m^3 | 71 km | 114 km |
|
|
|
|
### Finding 2: Wind Penalty Is Overrated
|
|
|
|
Data shows no meaningful penalty for wind on achieved distance:
|
|
|
|
| Wind | 10G Avg | 24G Avg | 47G Avg |
|
|
|------|---------|---------|---------|
|
|
| Calm (0-3 kts) | 216 | 84 | -- |
|
|
| Light (3-7 kts) | 214 | 100 | 77 |
|
|
| Moderate (7-12 kts) | 220 | 113 | 77 |
|
|
|
|
Wind may reduce inversion quality but also creates boundary-layer dynamics that can enhance propagation. Weight reduced from 18% to 8%.
|
|
|
|
### Finding 3: Low Pressure Correlates with Longer Distances
|
|
|
|
Contradicts the original model's high-pressure = good assumption:
|
|
|
|
| Pressure | 10G Avg | 10G P90 | 24G Avg | 47G Avg |
|
|
|----------|---------|---------|---------|---------|
|
|
| <1010 | 262 | 506 | 119 | 111 |
|
|
| 1015-1020 | 217 | 383 | 97 | 74 |
|
|
| 1025+ | 196 | 354 | 122 | -- |
|
|
|
|
Low pressure systems bring frontal boundaries with strong temperature/moisture gradients that create inversions and ducts. The key is gradient structure, not absolute pressure.
|
|
|
|
### Finding 4: Boundary Layer Depth — Time-Dependent Sweet Spot
|
|
|
|
Sounding data (3,901 profiles) reveals that BL depth interpretation depends on sounding time:
|
|
|
|
| Sounding Time | Avg BL Depth | Ducting % | Mechanism |
|
|
|---------------|-------------|-----------|-----------|
|
|
| 00Z (evening) | 1,370m | 54.0% | Elevated ducts within deep residual BL |
|
|
| 12Z (morning) | 335m | 53.7% | Surface/radiation ducts from shallow nocturnal BL |
|
|
|
|
Ducting rate is identical at both times (~54%), but via different mechanisms. At 12Z, shallow BL (<500m) is the signature. At 00Z, the duct is typically elevated within a deeper residual boundary layer. A single BL depth threshold won't work across both regimes — use BL depth relative to expected diurnal range.
|
|
|
|
HRRR profile data (4,522 profiles) confirms the relationship is monotonic:
|
|
|
|
| BL Depth Bin | Count | Avg N | Avg Min Gradient |
|
|
|-------------|-------|-------|------------------|
|
|
| 0-200m | 1,096 | 342.6 | -93.7 |
|
|
| 200-500m | 1,091 | 346.6 | -81.5 |
|
|
| 500-1000m | 1,242 | 337.0 | -70.1 |
|
|
| 1000-1500m | 735 | 328.8 | -58.3 |
|
|
| 1500-2000m | 299 | 325.2 | -51.0 |
|
|
| 2000m+ | 60 | 309.4 | -45.4 |
|
|
|
|
### Finding 5: Binary Duct Detection Is Weak — Use Continuous Gradient
|
|
|
|
Ducting is the **majority case** in soundings: 2,099 ducting (53.8%) vs 1,800 non-ducting (46.2%). Binary detection has near-zero discriminating power.
|
|
|
|
| Ducting | Count | Avg N | Avg Min Gradient | Avg BL Depth | Avg K-Index | Avg LI |
|
|
|---------|-------|-------|------------------|-------------|-------------|--------|
|
|
| No | 1,800 | 327.4 | -123.4 | 986m | 16.7 | 22.8 |
|
|
| Yes | 2,099 | 340.2 | -388.7 | 738m | 12.7 | 25.3 |
|
|
|
|
The continuous gradient (-389 vs -123) is the real signal — a 3x magnitude difference. HRRR data shows 79% of profiles in "Enhanced" regime (gradient -40 to -100), so the scoring must discriminate *within* the enhanced category, not just between standard and enhanced.
|
|
|
|
**Stability indices and ducting:**
|
|
- K-index is **lower** for ducting (12.7 vs 16.7) — stable atmosphere favors ducting, not convection
|
|
- Lifted Index is **higher** for ducting (25.3 vs 22.8) — confirms stability correlation
|
|
- **Precipitable water is identical** (28.0 mm) for both ducting and non-ducting — PWAT is NOT a useful ducting discriminator. It plateaus as a gradient predictor above ~15mm.
|
|
|
|
### Finding 6: Diurnal Signal Variation Sets a Noise Floor
|
|
|
|
Commercial link data shows 1-5 dB daily variation even on perfectly clear, stable days. The algorithm should convey that even an "EXCELLENT" score has +/-2-3 dB inherent uncertainty.
|
|
|
|
### Finding 7: LOS vs Beyond-LOS Regimes Are Inverted
|
|
|
|
On short LOS paths (3-7 km), sub-refractive conditions (dN/dh > -40/km) produce the best signal — minimal multipath, clean beam coupling. On long beyond-LOS paths (50-500+ km), enhanced refraction/ducting is essential. The algorithm must handle both regimes.
|
|
|
|
### Finding 8: Time-of-Day Effect Scales with Frequency
|
|
|
|
Night/dawn (22Z-10Z) enhancement vs afternoon baseline, from QSO distance data:
|
|
|
|
| Band | Afternoon Avg | Night/Dawn Avg | Enhancement | P90 Boost |
|
|
|------|--------------|----------------|-------------|-----------|
|
|
| 10 GHz | 209.7 km | 218.6 km | +4% | +8% |
|
|
| 24 GHz | 93.8 km | 119.7 km | +28% | +16% |
|
|
| 47 GHz | 63.5 km | 86.6 km | +36% | +42% |
|
|
| 75 GHz | 38.1 km | 175.4 km | +360% | +237% |
|
|
|
|
At 10 GHz the effect is modest. At 47+ GHz it is the **dominant variable**, more important than most weather parameters. The 75 GHz result is from only 20 night/dawn QSOs but the 4.6x multiplier is consistent with strong ducting being the *only* path at that frequency.
|
|
|
|
### Finding 9: Ducting Peaks June-July, Not August
|
|
|
|
Monthly ducting probability from 3,901 soundings:
|
|
|
|
| Month | Soundings | Ducting % | Avg N | Avg Min Gradient | Avg PWAT (mm) |
|
|
|-------|-----------|-----------|-------|------------------|---------------|
|
|
| Jan | 32 | 21.9% | 306.2 | -133.4 | 9.5 |
|
|
| Feb | 58 | 17.2% | 304.4 | -132.7 | 6.6 |
|
|
| Mar | 37 | **10.8%** | 293.3 | -112.5 | 6.6 |
|
|
| Apr | 64 | 37.5% | 306.0 | -152.0 | 14.0 |
|
|
| May | 68 | 48.5% | 323.3 | -233.1 | 20.5 |
|
|
| Jun | 134 | **68.7%** | 335.0 | -322.7 | 28.6 |
|
|
| Jul | 85 | **76.5%** | 339.3 | -301.4 | 27.6 |
|
|
| Aug | 1,700 | 53.9% | 341.9 | -260.7 | 33.3 |
|
|
| Sep | 1,564 | 56.4% | 332.4 | -286.5 | 26.2 |
|
|
| Oct | 48 | 60.4% | 321.5 | -314.3 | 17.9 |
|
|
| Nov | 53 | 56.6% | 308.4 | -264.3 | 10.6 |
|
|
| Dec | 58 | 12.1% | 305.0 | -139.8 | 10.5 |
|
|
|
|
**March is the worst month** (10.8%), not winter. December-February averages ~17%. The sharp ramp April→July is nonlinear. Contest data (Aug-Sep) slightly undersamples the true ducting peak (Jun-Jul at 69-77%). Note: Aug/Sep have disproportionate sounding counts because data is enriched from QSO time windows during contest months.
|
|
|
|
### Finding 10: Mode Matters — CW Has 24% Range Advantage
|
|
|
|
| Mode | QSOs | Avg Distance |
|
|
|------|------|-------------|
|
|
| CW | 19,863 | 232.2 km |
|
|
| PH (SSB) | 37,077 | 186.5 km |
|
|
| FM | 178 | 144.1 km |
|
|
| DG (digital) | 122 | 192.4 km |
|
|
|
|
The CW advantage (~7 dB bandwidth: 10*log10(2700/500)) translates to 24% longer average distances. Scoring tier range estimates should be qualified by mode. One confirmed 710 km rainscatter contact at 24 GHz (CW mode).
|
|
|
|
---
|
|
|
|
## Part 3: Band Configuration
|
|
|
|
```elixir
|
|
@band_configs %{
|
|
10_000 => %{
|
|
label: "10 GHz",
|
|
o2_db_km: 0.008,
|
|
h2o_coeff: 0.0005,
|
|
humidity_effect: :beneficial, # More moisture = more refractivity = longer paths
|
|
humidity_penalty: 0.0,
|
|
rain_k: 0.010, rain_alpha: 1.28,
|
|
# Seasonal pattern INVERTED from 24+ GHz: ducting + humidity both help 10G
|
|
# Ducting peaks Jun-Jul (69-77%), March worst (10.8%), Dec-Feb ~12-22%
|
|
# Score tracks ducting probability scaled to 0-100
|
|
seasonal_base: %{1 => 38, 2 => 32, 3 => 22, 4 => 55, 5 => 68,
|
|
6 => 90, 7 => 95, 8 => 75, 9 => 78, 10 => 82,
|
|
11 => 78, 12 => 25},
|
|
seasonal_adj: %{},
|
|
typical_range_km: 200,
|
|
extended_range_km: 500,
|
|
exceptional_range_km: 1000
|
|
},
|
|
24_000 => %{
|
|
label: "24 GHz",
|
|
o2_db_km: 0.015,
|
|
h2o_coeff: 0.012, # Validated by commercial link data
|
|
humidity_effect: :harmful, # 22.235 GHz H2O line shoulder
|
|
humidity_penalty: 1.6,
|
|
rain_k: 0.070, rain_alpha: 1.07,
|
|
# 24G: humidity hurts, so best in dry months. But March is worst for ducting.
|
|
# Balance: winter dry + some ducting > spring dry + no ducting
|
|
seasonal_base: %{1 => 88, 2 => 84, 3 => 68, 4 => 62, 5 => 51,
|
|
6 => 34, 7 => 18, 8 => 18, 9 => 48, 10 => 68,
|
|
11 => 96, 12 => 88},
|
|
seasonal_adj: %{5 => -4, 6 => -8, 7 => -10, 8 => -10, 9 => -4},
|
|
typical_range_km: 100,
|
|
extended_range_km: 250,
|
|
exceptional_range_km: 500
|
|
},
|
|
47_000 => %{
|
|
label: "47 GHz",
|
|
o2_db_km: 0.045,
|
|
h2o_coeff: 0.003,
|
|
humidity_effect: :harmful,
|
|
humidity_penalty: 1.0, # Window band, moderate H2O sensitivity
|
|
rain_k: 0.187, rain_alpha: 0.93,
|
|
seasonal_base: %{1 => 90, 2 => 88, 3 => 78, 4 => 68, 5 => 55,
|
|
6 => 38, 7 => 22, 8 => 22, 9 => 48, 10 => 74,
|
|
11 => 96, 12 => 90},
|
|
seasonal_adj: %{},
|
|
typical_range_km: 70,
|
|
extended_range_km: 150,
|
|
exceptional_range_km: 300
|
|
},
|
|
68_000 => %{
|
|
label: "68 GHz",
|
|
o2_db_km: 0.90, # 60 GHz O2 band wing — validated by link data
|
|
h2o_coeff: 0.007, # Measured: ~0.1 dB/km per g/m^3 on 2.8 km path
|
|
humidity_effect: :harmful,
|
|
humidity_penalty: 1.4,
|
|
rain_k: 0.310, rain_alpha: 0.86,
|
|
seasonal_base: %{1 => 90, 2 => 88, 3 => 78, 4 => 65, 5 => 50,
|
|
6 => 32, 7 => 18, 8 => 18, 9 => 44, 10 => 70,
|
|
11 => 92, 12 => 90},
|
|
seasonal_adj: %{},
|
|
typical_range_km: 40,
|
|
extended_range_km: 80,
|
|
exceptional_range_km: 150
|
|
},
|
|
75_000 => %{
|
|
label: "75 GHz",
|
|
o2_db_km: 0.012,
|
|
h2o_coeff: 0.006,
|
|
humidity_effect: :harmful,
|
|
humidity_penalty: 1.2,
|
|
rain_k: 0.345, rain_alpha: 0.84,
|
|
seasonal_base: %{1 => 90, 2 => 90, 3 => 80, 4 => 68, 5 => 55,
|
|
6 => 38, 7 => 22, 8 => 22, 9 => 48, 10 => 74,
|
|
11 => 96, 12 => 90},
|
|
seasonal_adj: %{},
|
|
typical_range_km: 50,
|
|
extended_range_km: 120,
|
|
exceptional_range_km: 250
|
|
},
|
|
122_000 => %{
|
|
label: "122 GHz",
|
|
o2_db_km: 0.80, # 118.75 GHz O2 wing — weather independent
|
|
h2o_coeff: 0.010,
|
|
humidity_effect: :harmful,
|
|
humidity_penalty: 1.0,
|
|
rain_k: 0.498, rain_alpha: 0.77,
|
|
seasonal_base: %{1 => 92, 2 => 90, 3 => 78, 4 => 62, 5 => 45,
|
|
6 => 28, 7 => 15, 8 => 15, 9 => 38, 10 => 68,
|
|
11 => 92, 12 => 92},
|
|
seasonal_adj: %{},
|
|
typical_range_km: 30,
|
|
extended_range_km: 80,
|
|
exceptional_range_km: 140
|
|
},
|
|
134_000 => %{
|
|
label: "134 GHz",
|
|
o2_db_km: 0.08,
|
|
h2o_coeff: 0.015,
|
|
humidity_effect: :harmful,
|
|
humidity_penalty: 1.3,
|
|
rain_k: 0.520, rain_alpha: 0.75,
|
|
seasonal_base: %{1 => 92, 2 => 90, 3 => 78, 4 => 65, 5 => 48,
|
|
6 => 30, 7 => 18, 8 => 18, 9 => 42, 10 => 70,
|
|
11 => 92, 12 => 92},
|
|
seasonal_adj: %{},
|
|
typical_range_km: 40,
|
|
extended_range_km: 100,
|
|
exceptional_range_km: 160
|
|
},
|
|
241_000 => %{
|
|
label: "241 GHz",
|
|
o2_db_km: 0.08,
|
|
h2o_coeff: 0.30, # Extreme H2O sensitivity (183/325 GHz lines)
|
|
humidity_effect: :harmful,
|
|
humidity_penalty: 3.0,
|
|
rain_k: 0.550, rain_alpha: 0.70,
|
|
seasonal_base: %{1 => 95, 2 => 92, 3 => 75, 4 => 55, 5 => 35,
|
|
6 => 15, 7 => 8, 8 => 8, 9 => 30, 10 => 65,
|
|
11 => 95, 12 => 95},
|
|
seasonal_adj: %{},
|
|
typical_range_km: 10,
|
|
extended_range_km: 50,
|
|
exceptional_range_km: 115
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Part 4: Scoring Functions (Beyond-LOS Regime)
|
|
|
|
All scores return 0-100. The beyond-LOS regime is the primary use case for ham radio propagation prediction.
|
|
|
|
### 1. Humidity Score — Frequency-Dependent
|
|
|
|
The critical insight: moisture helps at 10 GHz (refractivity) and hurts at 24+ GHz (absorption).
|
|
|
|
```elixir
|
|
def score_humidity(abs_humidity_gm3, band_config) do
|
|
case band_config.humidity_effect do
|
|
:beneficial ->
|
|
# 10 GHz: more moisture = higher surface N = more beam bending
|
|
# Extreme humidity risks scintillation
|
|
cond do
|
|
abs_humidity_gm3 < 4 -> 55 # Very dry — poor refractivity
|
|
abs_humidity_gm3 < 7 -> 70 # Dry
|
|
abs_humidity_gm3 < 10 -> 82 # Moderate
|
|
abs_humidity_gm3 < 14 -> 90 # Good refractivity
|
|
abs_humidity_gm3 < 18 -> 95 # Excellent refractivity
|
|
abs_humidity_gm3 < 22 -> 88 # High — scintillation onset
|
|
true -> 75 # Tropical — scintillation risk
|
|
end
|
|
|
|
:harmful ->
|
|
# 24+ GHz: H2O absorption dominates
|
|
# Penalty factor scales by band (1.0 for 47G window, 1.6 for 24G near line, 3.0 for 241G)
|
|
r = abs_humidity_gm3 * band_config.humidity_penalty
|
|
cond do
|
|
r <= 6 -> 100
|
|
r <= 9 -> round(95 - (r - 6) / 3 * 20)
|
|
r <= 13 -> round(75 - (r - 9) / 4 * 30)
|
|
r <= 18 -> round(45 - (r - 13) / 5 * 35)
|
|
true -> max(0, round(10 - (r - 18) * 2))
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
### 2. Time of Day Score — Inversion Lifecycle
|
|
|
|
The strongest diurnal predictor in the data. Dawn shows the highest P90 distances; late evening shows elevated averages.
|
|
|
|
```elixir
|
|
@sunrise_table [7.4, 7.3, 7.0, 6.7, 6.35, 6.25,
|
|
6.35, 6.65, 6.9, 7.1, 7.35, 7.45]
|
|
|
|
def score_time_of_day(utc_hour, utc_minute, month) do
|
|
offset = if month >= 3 and month <= 10, do: -5, else: -6 # CDT/CST
|
|
local = rem(utc_hour + utc_minute / 60 + offset + 24, 24)
|
|
sunrise = Enum.at(@sunrise_table, month - 1)
|
|
d = local - sunrise # hours relative to sunrise
|
|
|
|
cond do
|
|
d >= -1.5 and d <= 1.5 ->
|
|
{100, "Peak — inversion maximum"}
|
|
|
|
d > 1.5 and d <= 3.0 ->
|
|
{78, "Good — inversion eroding"}
|
|
|
|
d > -3.0 and d < -1.5 ->
|
|
{82, "Pre-dawn — inversion building"}
|
|
|
|
d > 3.0 and d <= 6.0 ->
|
|
{38, "Marginal — boundary layer mixing"}
|
|
|
|
local >= 20.0 or local <= 1.0 ->
|
|
{72, "Evening — cooling, inversion reforming"}
|
|
|
|
d > 6.0 ->
|
|
{18, "Afternoon — full convective mixing"}
|
|
|
|
true ->
|
|
{55, "Night — gradual cooling"}
|
|
end
|
|
end
|
|
```
|
|
|
|
### 3. Temperature-Dewpoint Depression — Frequency-Split
|
|
|
|
Large depression = dry aloft = favorable for 24+ GHz. Small depression = moist = favorable for 10 GHz refractivity (but near-saturation risks fog).
|
|
|
|
```elixir
|
|
def score_td_depression(temp_f, dewpoint_f, band_config) do
|
|
dep = temp_f - dewpoint_f
|
|
|
|
case band_config.humidity_effect do
|
|
:beneficial ->
|
|
cond do
|
|
dep < 3 -> 40 # Near saturation — fog/scattering risk
|
|
dep < 8 -> 75 # Moist — good refractivity
|
|
dep < 14 -> 85 # Moderate — balanced
|
|
dep < 22 -> 70 # Dry — reduced refractivity
|
|
true -> 55 # Very dry — poor refractivity
|
|
end
|
|
|
|
:harmful ->
|
|
cond do
|
|
dep > 22 -> 96 # Very dry aloft
|
|
dep > 14 -> 80 # Good stability
|
|
dep > 8 -> 60 # Moderate
|
|
dep > 4 -> 38 # Marginal
|
|
true -> 18 # Humid aloft
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
### 4. Sky Cover Score
|
|
|
|
Data shows modest impact at 24/47 GHz, near-zero at 10 GHz. VV (vertical visibility / fog) is a moderate penalty due to near-surface moisture content. Supports both METAR categories (from ASOS) and percentage sky cover (from IEMRE gridded data).
|
|
|
|
```elixir
|
|
def score_sky(condition) when is_binary(condition) do
|
|
# METAR category from ASOS
|
|
case condition do
|
|
c when c in ["CLR", "SKC"] -> 100
|
|
"FEW" -> 88
|
|
"SCT" -> 60
|
|
"BKN" -> 25
|
|
"OVC" -> 5
|
|
"VV" -> 5
|
|
_ -> 50
|
|
end
|
|
end
|
|
|
|
def score_sky(pct) when is_number(pct) do
|
|
# Percentage sky cover from IEMRE (0-100%)
|
|
cond do
|
|
pct <= 6 -> 100 # CLR equivalent
|
|
pct <= 25 -> 88 # FEW
|
|
pct <= 50 -> 60 # SCT
|
|
pct <= 87 -> 25 # BKN
|
|
true -> 5 # OVC
|
|
end
|
|
end
|
|
```
|
|
|
|
### 5. Season Score
|
|
|
|
Per-band lookup with optional adjustments. 24 GHz gets additional summer penalties due to Gulf moisture.
|
|
|
|
```elixir
|
|
def score_season(month, band_config) do
|
|
base = Map.get(band_config.seasonal_base, month, 50)
|
|
adj = Map.get(band_config.seasonal_adj, month, 0)
|
|
max(0, min(100, base + adj))
|
|
end
|
|
```
|
|
|
|
### 6. Wind Score — Reduced Weight
|
|
|
|
Data shows minimal impact on achieved distance. Retain mild penalty only for very high winds (turbulent scintillation).
|
|
|
|
```elixir
|
|
def score_wind(speed_kts) do
|
|
cond do
|
|
speed_kts < 5 -> 100
|
|
speed_kts < 10 -> 90
|
|
speed_kts < 15 -> 75
|
|
speed_kts < 20 -> 55
|
|
speed_kts < 25 -> 35
|
|
true -> 15
|
|
end
|
|
end
|
|
```
|
|
|
|
### 7. Rain Score
|
|
|
|
Not validated by measured data. Based on ITU-R P.838-3 attenuation per km. At 10 GHz, moderate rain is tolerable. Above 75 GHz, even light rain effectively kills the path.
|
|
|
|
```elixir
|
|
def score_rain(rain_rate_mmhr, band_config) do
|
|
if rain_rate_mmhr == nil or rain_rate_mmhr == 0 do
|
|
100
|
|
else
|
|
gamma = band_config.rain_k * :math.pow(rain_rate_mmhr, band_config.rain_alpha)
|
|
cond do
|
|
gamma < 0.1 -> 95
|
|
gamma < 0.5 -> 75
|
|
gamma < 1.0 -> 50
|
|
gamma < 2.0 -> 25
|
|
gamma < 5.0 -> 10
|
|
true -> 0
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
### 8. Pressure Score — Gradient-Focused
|
|
|
|
Data contradicts the original high-pressure-is-good model. Frontal boundaries (changing pressure) create the strongest refractive gradients. Rising post-frontal pressure and slowly falling pre-frontal pressure both score well.
|
|
|
|
```elixir
|
|
def score_pressure(current_mb, previous_mb) do
|
|
case previous_mb do
|
|
nil ->
|
|
# No trend — mild scoring on absolute value
|
|
cond do
|
|
current_mb > 1025 -> 55 # Strong ridge
|
|
current_mb > 1018 -> 65 # Mild high
|
|
current_mb > 1010 -> 60 # Normal
|
|
current_mb > 1005 -> 55 # Low
|
|
true -> 40 # Very low — active weather
|
|
end
|
|
|
|
prev ->
|
|
delta = current_mb - prev
|
|
cond do
|
|
delta > 2.5 -> 80 # Rising rapidly — post-frontal clearing
|
|
delta > 0.8 -> 70 # Rising — stabilizing
|
|
delta > -0.5 -> 60 # Steady — neutral
|
|
delta > -2.0 -> 65 # Falling slowly — approaching front, duct possible
|
|
true -> 45 # Falling rapidly — active weather
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
### 9. Refractivity Score — When Sounding/HRRR Data Available
|
|
|
|
Best predictor when available. Binary duct detection is useless (54% baseline rate). Use continuous gradient magnitude and BL depth instead. Surface-based ducts (base=0) matter more than elevated ducts.
|
|
|
|
From sounding data: avg gradient -389 N/km for ducting events vs -123 for non-ducting. From HRRR: BL depth < 200m correlates with avg gradient of -93.7 (approaching super-refraction).
|
|
|
|
```elixir
|
|
def score_refractivity(sounding_or_hrrr, band_config) do
|
|
cond do
|
|
sounding_or_hrrr == nil -> 50 # No data — neutral
|
|
|
|
sounding_or_hrrr.min_dn_dh < -500 ->
|
|
# Strong ducting — gradient 3x non-ducting average
|
|
case band_config.humidity_effect do
|
|
:beneficial -> 98 # 10 GHz benefits most from ducting
|
|
:harmful -> 85 # Higher bands benefit but absorption limits range
|
|
end
|
|
|
|
sounding_or_hrrr.min_dn_dh < -300 ->
|
|
# Moderate ducting — near ducting average of -389
|
|
case band_config.humidity_effect do
|
|
:beneficial -> 92
|
|
:harmful -> 78
|
|
end
|
|
|
|
sounding_or_hrrr.min_dn_dh < -200 ->
|
|
80 # Enhanced refraction — above non-ducting average of -123
|
|
|
|
# BL depth signal: shallow BL at 12Z = strong surface duct potential
|
|
sounding_or_hrrr.bl_depth_m != nil and
|
|
sounding_or_hrrr.bl_depth_m < 300 ->
|
|
82 # Very shallow BL — strong inversion cap (12Z signature)
|
|
|
|
sounding_or_hrrr.min_dn_dh < -100 ->
|
|
65 # Mild enhancement
|
|
|
|
sounding_or_hrrr.min_dn_dh < -60 ->
|
|
55 # Slightly enhanced — within the broad "Enhanced" HRRR regime
|
|
|
|
true ->
|
|
42 # Standard/sub-refractive — HRRR shows only 5% of profiles here
|
|
end
|
|
end
|
|
```
|
|
|
|
---
|
|
|
|
## Part 5: Composite Score
|
|
|
|
### Weights
|
|
|
|
Revised from expanded dataset. Key changes: refractivity weight doubled (HRRR now provides hourly data, not just 12-hourly soundings), time-of-day weight increased (data shows it's the dominant variable above 24 GHz), pressure reduced (weak standalone predictor, 5% was still generous).
|
|
|
|
| Factor | Weight | Rationale |
|
|
|--------|--------|-----------|
|
|
| Humidity | 20% | Dominant variable, but split role by frequency |
|
|
| Time of Day | 20% | First-order effect at 24+ GHz (28-360% enhancement at night) |
|
|
| Td Depression | 12% | Proxy for humidity aloft — strong signal |
|
|
| Refractivity | 10% | Best predictor; HRRR provides hourly coverage, sounding gradient is 3x discriminator |
|
|
| Sky Cover | 10% | Modest effect, mainly at higher frequencies |
|
|
| Season | 10% | Long-term baseline — now ducting-calibrated per band |
|
|
| Wind | 6% | Data shows minimal impact; penalty only for extremes |
|
|
| Rain | 8% | Critical for 24+ GHz paths |
|
|
| Pressure | 4% | Weak standalone predictor |
|
|
|
|
```elixir
|
|
def composite_score(factors) do
|
|
round(
|
|
factors.humidity * 0.20 +
|
|
factors.time_of_day * 0.20 +
|
|
factors.td_depression * 0.12 +
|
|
factors.refractivity * 0.10 +
|
|
factors.sky * 0.10 +
|
|
factors.season * 0.10 +
|
|
factors.wind * 0.06 +
|
|
factors.rain * 0.08 +
|
|
factors.pressure * 0.04
|
|
)
|
|
end
|
|
```
|
|
|
|
### Score Tiers with Per-Band Range Estimates
|
|
|
|
Range estimates are for CW mode. For SSB/phone, reduce by ~20%. For FM, reduce by ~40%.
|
|
|
|
Database stats for reference: 10G avg=213 km, P90=383 km, max=2,393 km. 24G avg=98 km, P90=179 km, max=710 km. 47G avg=66 km, P90=122 km, max=343 km. 75G avg=64 km, P90=177 km, max=289 km.
|
|
|
|
| Score | Label | 10G | 24G | 47G | 75G |
|
|
|-------|-------|-----|-----|-----|-----|
|
|
| 80-100 | EXCELLENT | 400-2000+ km | 200-500 km | 120-300 km | 80-200+ km |
|
|
| 65-79 | GOOD | 250-400 km | 120-200 km | 80-120 km | 50-80 km |
|
|
| 50-64 | MARGINAL | 150-250 km | 70-120 km | 50-80 km | 30-50 km |
|
|
| 33-49 | POOR | 80-150 km | 40-70 km | 25-50 km | 15-30 km |
|
|
| 0-32 | NEGLIGIBLE | <80 km | <40 km | <25 km | <15 km |
|
|
|
|
| Color | Hex |
|
|
|-------|-----|
|
|
| EXCELLENT | #00ffa3 |
|
|
| GOOD | #7dffd4 |
|
|
| MARGINAL | #ffe566 |
|
|
| POOR | #ff9044 |
|
|
| NEGLIGIBLE | #ff4f4f |
|
|
|
|
---
|
|
|
|
## Part 6: LOS Regime Scoring
|
|
|
|
For known fixed links or short paths with confirmed Fresnel clearance. Key difference: sub-refraction is neutral/beneficial (minimal multipath), and gaseous absorption is the primary variable.
|
|
|
|
### LOS Refractivity Score
|
|
|
|
```elixir
|
|
def score_refractivity_los(dn_dh) do
|
|
cond do
|
|
dn_dh > 0 -> 60 # Strong sub-refraction — unusual but not harmful
|
|
dn_dh > -30 -> 85 # Moderate sub-refraction — stable, clean signal
|
|
dn_dh > -40 -> 75 # Near standard
|
|
dn_dh > -80 -> 60 # Enhanced — multipath onset
|
|
dn_dh > -157 -> 45 # Strong enhancement — multipath likely
|
|
true -> 30 # Super-refraction — significant multipath fading
|
|
end
|
|
end
|
|
```
|
|
|
|
### LOS Surface N Score
|
|
|
|
Higher N often means more moisture = more absorption at 24+ GHz. Validated by link data: N < 310 gave best 68 GHz signal, N > 340 gave worst.
|
|
|
|
```elixir
|
|
def score_surface_n(n_value, band_config) do
|
|
case band_config.humidity_effect do
|
|
:beneficial ->
|
|
cond do
|
|
n_value > 350 -> 90
|
|
n_value > 330 -> 80
|
|
n_value > 315 -> 65
|
|
n_value > 300 -> 50
|
|
true -> 35
|
|
end
|
|
|
|
:harmful ->
|
|
cond do
|
|
n_value < 300 -> 90
|
|
n_value < 315 -> 80
|
|
n_value < 330 -> 65
|
|
n_value < 345 -> 50
|
|
true -> 35
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
### LOS vs Beyond-LOS Selection
|
|
|
|
```elixir
|
|
def compute_score(conditions, band_config, path_type \\ :beyond_los) do
|
|
base_factors = %{
|
|
humidity: score_humidity(conditions.abs_humidity, band_config),
|
|
wind: score_wind(conditions.wind_speed_kts),
|
|
sky: score_sky(conditions.sky_condition),
|
|
time_of_day: score_time_of_day(conditions.utc_hour, conditions.utc_minute, conditions.month) |> elem(0),
|
|
td_depression: score_td_depression(conditions.temp_f, conditions.dewpoint_f, band_config),
|
|
season: score_season(conditions.month, band_config),
|
|
pressure: score_pressure(conditions.slp, conditions.prev_slp),
|
|
rain: score_rain(conditions.rain_rate, band_config)
|
|
}
|
|
|
|
factors = case path_type do
|
|
:beyond_los ->
|
|
Map.put(base_factors, :refractivity,
|
|
score_refractivity(conditions.sounding, band_config))
|
|
|
|
:los ->
|
|
Map.put(base_factors, :refractivity,
|
|
score_refractivity_los(conditions.dn_dh))
|
|
end
|
|
|
|
%{score: composite_score(factors), factors: factors}
|
|
end
|
|
```
|
|
|
|
---
|
|
|
|
## Part 7: Link Budget
|
|
|
|
For point-to-point path analysis with known station parameters.
|
|
|
|
### EIRP
|
|
|
|
```
|
|
eirp_dbm = tx_power_dbm + tx_antenna_dbi - feed_loss_db
|
|
```
|
|
|
|
### Receiver Sensitivity
|
|
|
|
```
|
|
sensitivity_dbm = -174 + noise_figure_db + 10 * log10(bandwidth_hz)
|
|
|
|
CW: bandwidth = 500 Hz
|
|
SSB: bandwidth = 2700 Hz
|
|
```
|
|
|
|
### Total Path Loss
|
|
|
|
```
|
|
total_loss = FSPL + gaseous_absorption + rain_attenuation + diffraction_loss - duct_enhancement
|
|
|
|
gaseous_absorption = (o2_db_km + h2o_coeff * rho) * distance_km
|
|
rain_attenuation = gamma_R * distance_km * rain_effective_fraction
|
|
```
|
|
|
|
### Duct Enhancement (Beyond-LOS Only)
|
|
|
|
Calibrated against confirmed contacts:
|
|
|
|
```elixir
|
|
def duct_enhancement_db(prop_score) do
|
|
cond do
|
|
prop_score >= 80 -> -14 # 14 dB improvement
|
|
prop_score >= 65 -> -10
|
|
prop_score >= 50 -> -6
|
|
prop_score >= 33 -> -2
|
|
true -> 0
|
|
end
|
|
end
|
|
```
|
|
|
|
### Knife-Edge Diffraction (ITU-R P.526)
|
|
|
|
```elixir
|
|
def knife_edge_loss(v) do
|
|
cond do
|
|
v <= -0.7787 -> 0 # Clear
|
|
v <= 0 -> -20 * :math.log10(0.5 - 0.62 * v)
|
|
v <= 1 -> -20 * :math.log10(0.5 * :math.exp(-0.95 * v))
|
|
v <= 2.4 ->
|
|
inner = max(0, 0.1184 - (0.38 - 0.1 * v) ** 2)
|
|
-20 * :math.log10(0.4 - :math.sqrt(inner))
|
|
true -> 20 * :math.log10(v) + 13.0 # Asymptotic
|
|
end
|
|
end
|
|
```
|
|
|
|
### Success Probability
|
|
|
|
```elixir
|
|
def margin_to_success(margin_db, prop_score) do
|
|
margin_pct = cond do
|
|
margin_db <= 0 -> 0
|
|
margin_db <= 10 -> margin_db / 10 * 20
|
|
margin_db <= 15 -> 20 + (margin_db - 10) / 5 * 20
|
|
margin_db <= 20 -> 40 + (margin_db - 15) / 5 * 20
|
|
margin_db <= 25 -> 60 + (margin_db - 20) / 5 * 20
|
|
margin_db <= 30 -> 80 + (margin_db - 25) / 5 * 20
|
|
true -> 100
|
|
end
|
|
|
|
# Propagation modulation: score 100 -> x1.30, score 50 -> x1.00, score 0 -> x0.70
|
|
prop_factor = 0.70 + (prop_score / 100) * 0.60
|
|
max(0, min(99, round(margin_pct * prop_factor)))
|
|
end
|
|
```
|
|
|
|
### Note: Antenna Height & Duct Coupling Geometry
|
|
|
|
Antenna height and dish elevation angle affect how efficiently a station couples into an atmospheric duct. This is a real physical effect but is **second-order** to duct characteristics at the ranges this model targets (50-1000+ km).
|
|
|
|
**Why it's not in the scoring model:**
|
|
- At >300 km, the duct's own refractive gradient (k-factor) dominates over all antenna geometry. The required aim angle to graze a duct converges toward 0° regardless of antenna height.
|
|
- Antenna height differences in the 15-21m range (typical amateur stations) shift beam geometry by ~0.001° at long range — well within the ±2-3 dB noise floor from diurnal variation.
|
|
- The primary benefit of antenna height (50+ ft) is clearing local obstructions and ground clutter in the near field (0-20 km), not geometric coupling to the duct layer.
|
|
- VE4MA (50 ft, flat prairie) and W5LUA (70 ft, suburban) achieve similar range classes, confirming duct geometry is the dominant term.
|
|
|
|
**Where it matters — beamwidth vs frequency:**
|
|
At 10 GHz a typical 60cm dish has ~3° beamwidth, making elevation angle errors forgiving. At 24 GHz beamwidth shrinks to ~1.5°, at 47 GHz to <1°. A 0.3° aim error that is irrelevant at 10 GHz becomes a contact killer at 47 GHz. If station profiles (antenna height, dish size, elevation setting) are added in the future, a frequency-dependent **beamwidth coupling penalty** in `margin_to_success` would be the right integration point — penalizing paths where the required aim angle to the detected duct layer exceeds the antenna's half-power beamwidth.
|
|
|
|
---
|
|
|
|
## Part 8: Short-Term Prediction Model
|
|
|
|
### Approach
|
|
|
|
Extrapolate current conditions forward 1-6 hours using observed trends, diurnal models, and forecast data when available.
|
|
|
|
### Prediction Confidence
|
|
|
|
Based on commercial link signal prediction accuracy:
|
|
|
|
| Horizon | Observed Accuracy | Confidence |
|
|
|---------|-------------------|------------|
|
|
| Current | +/- 1 dB | 95% |
|
|
| +30 min | +/- 1.5 dB | 90% |
|
|
| +1 hr | +/- 2 dB | 85% |
|
|
| +2 hr | +/- 3 dB | 75% |
|
|
| +3 hr | +/- 4 dB | 60% |
|
|
| +6 hr | +/- 5 dB | 40% |
|
|
|
|
### Diurnal Temperature Model
|
|
|
|
```elixir
|
|
def project_temperature(current_temp_f, trend_per_hour, hours_ahead,
|
|
future_local_hour, month) do
|
|
sunrise = Enum.at(@sunrise_table, month - 1)
|
|
|
|
diurnal_rate = cond do
|
|
future_local_hour < sunrise - 1 -> -0.5 # Pre-dawn: slow cooling
|
|
future_local_hour < sunrise + 2 -> 0.0 # Sunrise transition
|
|
future_local_hour < 15 -> 2.0 # Morning: warming
|
|
future_local_hour < 18 -> 0.5 # Late afternoon
|
|
future_local_hour < 21 -> -1.5 # Evening: cooling
|
|
true -> -1.0 # Night: slow cooling
|
|
end
|
|
|
|
# Blend: current trend dominates short-term, diurnal model dominates long-term
|
|
weight = min(1.0, hours_ahead / 4.0)
|
|
blended_rate = trend_per_hour * (1.0 - weight) + diurnal_rate * weight
|
|
current_temp_f + blended_rate * hours_ahead
|
|
end
|
|
```
|
|
|
|
### Prediction Flow
|
|
|
|
```elixir
|
|
def predict_scores(current_obs, obs_3hr_ago, forecast, band_config) do
|
|
temp_trend = (current_obs.temp_f - obs_3hr_ago.temp_f) / 3
|
|
dp_trend = (current_obs.dewpoint_f - obs_3hr_ago.dewpoint_f) / 3
|
|
pressure_trend = (current_obs.slp - obs_3hr_ago.slp) / 3
|
|
|
|
for hours_ahead <- 1..6 do
|
|
future_time = DateTime.add(current_obs.observed_at, hours_ahead * 3600)
|
|
month = future_time.month
|
|
|
|
projected_temp = project_temperature(current_obs.temp_f, temp_trend,
|
|
hours_ahead, future_time.hour, month)
|
|
projected_dp = current_obs.dewpoint_f + dp_trend * hours_ahead
|
|
projected_slp = current_obs.slp + pressure_trend * hours_ahead
|
|
projected_sky = forecast_value(forecast, :sky, hours_ahead) || current_obs.sky_condition
|
|
projected_rain = forecast_value(forecast, :rain_rate, hours_ahead) || 0
|
|
projected_wind = forecast_value(forecast, :wind_kts, hours_ahead) || current_obs.wind_speed_kts
|
|
|
|
# Compute absolute humidity from projected values
|
|
tc = (projected_temp - 32) * 5 / 9
|
|
td_c = (projected_dp - 32) * 5 / 9
|
|
es = 6.112 * :math.exp(17.67 * tc / (tc + 243.5))
|
|
ed = 6.112 * :math.exp(17.67 * td_c / (td_c + 243.5))
|
|
rh = min(100, ed / es * 100)
|
|
abs_hum = 217 * (rh / 100) * es / (tc + 273.15)
|
|
|
|
factors = %{
|
|
humidity: score_humidity(abs_hum, band_config),
|
|
wind: score_wind(projected_wind),
|
|
sky: score_sky(projected_sky),
|
|
time_of_day: score_time_of_day(future_time.hour, future_time.minute, month) |> elem(0),
|
|
td_depression: score_td_depression(projected_temp, projected_dp, band_config),
|
|
season: score_season(month, band_config),
|
|
pressure: score_pressure(projected_slp, current_obs.slp),
|
|
rain: score_rain(projected_rain, band_config),
|
|
refractivity: 50 # Cannot predict from surface obs alone
|
|
}
|
|
|
|
%{
|
|
hours_ahead: hours_ahead,
|
|
time: future_time,
|
|
score: composite_score(factors),
|
|
factors: factors,
|
|
confidence: prediction_confidence(hours_ahead)
|
|
}
|
|
end
|
|
end
|
|
|
|
def prediction_confidence(hours_ahead) do
|
|
case hours_ahead do
|
|
1 -> 0.85
|
|
2 -> 0.75
|
|
3 -> 0.60
|
|
4 -> 0.50
|
|
5 -> 0.40
|
|
6 -> 0.30
|
|
_ -> 0.20
|
|
end
|
|
end
|
|
```
|
|
|
|
---
|
|
|
|
## Part 9: Sounding & Refractivity Analysis
|
|
|
|
### Refractivity Profile from Sounding
|
|
|
|
```elixir
|
|
def compute_refractivity_profile(levels, sfc_height_m) do
|
|
Enum.map(levels, fn level ->
|
|
t_k = level.temp_c + 273.15
|
|
e = 6.1121 * :math.exp((18.678 - level.temp_c / 234.5) * (level.temp_c / (257.14 + level.temp_c)))
|
|
e_actual = if level.dewpoint_c, do: 6.1121 * :math.exp((18.678 - level.dewpoint_c / 234.5) * (level.dewpoint_c / (257.14 + level.dewpoint_c))), else: 0
|
|
|
|
n = 77.6 * level.pressure_hpa / t_k + 3.73e5 * e_actual / (t_k * t_k)
|
|
h_agl = level.height_m - sfc_height_m
|
|
m = n + 0.157 * h_agl
|
|
|
|
%{height_agl: h_agl, n: n, m: m, temp_c: level.temp_c, dewpoint_c: level.dewpoint_c}
|
|
end)
|
|
end
|
|
```
|
|
|
|
### Duct Detection
|
|
|
|
Duct exists where dM/dh < 0. Filter for strength > 2 M-units.
|
|
|
|
```elixir
|
|
def detect_ducts(profile) do
|
|
profile
|
|
|> Enum.chunk_every(2, 1, :discard)
|
|
|> Enum.reduce({[], nil}, fn [below, above], {ducts, duct_start} ->
|
|
dm = above.m - below.m
|
|
|
|
cond do
|
|
dm < 0 and duct_start == nil ->
|
|
{ducts, %{base: below.height_agl, base_m: below.m}}
|
|
|
|
dm >= 0 and duct_start != nil ->
|
|
strength = duct_start.base_m - below.m
|
|
if strength > 2 do
|
|
duct = %{base: duct_start.base, top: below.height_agl, strength: strength}
|
|
{[duct | ducts], nil}
|
|
else
|
|
{ducts, nil}
|
|
end
|
|
|
|
true ->
|
|
{ducts, duct_start}
|
|
end
|
|
end)
|
|
|> elem(0)
|
|
|> Enum.reverse()
|
|
end
|
|
```
|
|
|
|
### Inversion Detection
|
|
|
|
Temperature increasing with height. Merge adjacent inversions within 200m gap. Filter: strength >= 0.5C, base < 5000m AGL.
|
|
|
|
### Stability Indices
|
|
|
|
```
|
|
K-Index = (T850 - T500) + Td850 - (T700 - Td700)
|
|
|
|
Lifted Index = T500 - (Tsfc - (h500 - h_sfc) * 0.00976)
|
|
LI < 0: Unstable (convection likely, inversion destroyed)
|
|
LI > 0: Stable (inversion maintained)
|
|
|
|
Precipitable Water = sum[(MR_i + MR_{i-1}) / 2 * dP / (9.81 * 10)]
|
|
MR = 622 * e / (P - e)
|
|
```
|
|
|
|
### Boundary Layer Depth
|
|
|
|
Find height where potential temperature (theta = T + 9.8 * h/1000) exceeds surface theta by 2C. The 500-1000m sweet spot indicates an elevated inversion — high enough to trap signals, not so deep that full mixing has occurred.
|
|
|
|
---
|
|
|
|
## Part 10: Band-Specific Propagation Mechanisms
|
|
|
|
### Coupling Sensitivity by Frequency
|
|
|
|
Duct coupling geometry becomes increasingly critical at higher frequencies due to narrower antenna beamwidths. A dish aimed 0.3° away from the optimal duct grazing angle:
|
|
- **10 GHz** (~3° beamwidth): Still within half-power beam — negligible loss
|
|
- **24 GHz** (~1.5° beamwidth): Approaching beam edge — moderate coupling loss
|
|
- **47 GHz** (<1° beamwidth): Outside half-power beam — potential contact killer
|
|
- **75+ GHz** (<0.5° beamwidth): Precision aim required — elevation error dominates
|
|
|
|
For surface ducts, the beam must arrive at <0.5° grazing incidence to be trapped. For elevated ducts (500-1500m AGL), the optimal elevation angle is path-distance dependent: slightly positive at close range, near-zero at the "sweet spot" distance, and slightly negative at extreme range due to Earth curvature.
|
|
|
|
### 10 GHz (3cm) — Tropospheric Ducting Band
|
|
|
|
**Primary mechanisms:** Ducting, enhanced refraction
|
|
**Key variable:** Refractivity profile, NOT humidity absorption (0.012 dB/km total is negligible)
|
|
**Best conditions:** Moderate-high humidity (12-20 g/m^3), temperature inversions, stable atmosphere, late evening through early morning
|
|
**Best months:** June-July (ducting probability 69-77%). August contest data undersamples peak season.
|
|
**Worst month:** March (10.8% ducting — worse than deep winter)
|
|
**Dataset:** 53,013 QSOs, avg 213 km, P90 383 km, max 2,393 km. CW avg 232 km vs PH avg 187 km.
|
|
**Unique:** Largely insensitive to rain. Can propagate through cloud decks. Marine ducting produces 1000+ km coastal paths. Frontal boundaries create strong refractive gradients. 97.2% of paths are terrain-blocked — ducting IS the propagation mechanism.
|
|
|
|
### 24 GHz (1.2cm) — Water Vapor Line Band
|
|
|
|
**Primary mechanisms:** Ducting (reduced by absorption), enhanced refraction
|
|
**Key variable:** Absolute humidity (22.235 GHz H2O line makes this THE most humidity-sensitive band)
|
|
**Best conditions:** Very dry air (<8 g/m^3), cold season (Nov-Mar), clear skies, pre-dawn through early morning
|
|
**Night enhancement:** +28% avg distance, +16% P90 vs afternoon (119.7 km vs 93.8 km)
|
|
**Dataset:** 3,639 QSOs, avg 98 km, P90 179 km, max 710 km (rainscatter CW).
|
|
**Unique:** 10x more sensitive to water vapor than 10 GHz. Summer Gulf moisture devastates range. Rain scatter is a viable alternative mechanism (710 km QSO documented). March is the worst ducting month (10.8%) but also has low humidity, creating a tension between ducting availability and absorption loss.
|
|
|
|
### 47 GHz (6mm) — Atmospheric Window
|
|
|
|
**Primary mechanisms:** Ducting (in atmospheric window), enhanced LOS
|
|
**Key variable:** Balance of humidity and refractivity; very dry air dramatically helps
|
|
**Best conditions:** Dry air (<8 g/m^3), clear skies, strong inversions, early morning
|
|
**Night enhancement:** +36% avg distance, +42% P90 vs afternoon (86.6 km vs 63.5 km). Time-of-day is the dominant variable at this frequency.
|
|
**Dataset:** 689 QSOs, avg 66 km, P90 122 km, max 343 km.
|
|
**Unique:** Window between 22 GHz H2O and 60 GHz O2. O2 absorption ~0.045 dB/km is fixed. Ducting is the ONLY way beyond ~150 km.
|
|
|
|
### 68 GHz — V-Band Edge
|
|
|
|
**Primary mechanisms:** LOS only (O2 absorption limits range)
|
|
**Key variable:** O2 wing absorption (~0.9 dB/km, weather-independent) + humidity
|
|
**Best conditions:** Cold/dry air, no precipitation, short paths
|
|
**Unique:** Validated by link data showing 3-5 dB diurnal fades on 2.8 km path. O2 absorption caps practical range regardless of conditions. Viable for short links (<5 km), very challenging for beyond-LOS.
|
|
|
|
### 75 GHz (4mm) — Window Band
|
|
|
|
**Primary mechanisms:** Rare ducting, enhanced LOS
|
|
**Key variable:** Dry air + no precipitation
|
|
**Best conditions:** Very dry (<5 g/m^3), no rain, strong inversions, winter, night/dawn
|
|
**Night enhancement:** +360% avg distance vs afternoon (175.4 km vs 38.1 km). At this frequency, nighttime propagation is essentially a different regime. Daytime contacts are limited to ~40 km; nighttime contacts regularly exceed 150 km.
|
|
**Dataset:** 104 QSOs, avg 64 km, P90 177 km, max 289 km.
|
|
**Unique:** 289 km record (California marine duct). Rain attenuation severe (~1 dB/km at 4 mm/hr).
|
|
|
|
### 122 GHz (2.5mm) — O2 Line Wing
|
|
|
|
**Primary mechanisms:** Enhanced LOS, rare ducting
|
|
**Key variable:** O2 absorption from 118.75 GHz line (~0.8 dB/km, cannot be improved by weather)
|
|
**Best conditions:** Cold temperatures (reduce O2 line broadening), very dry, no rain
|
|
**Unique:** 139 km record (California, February). Practically limited to ~50 km reliable paths.
|
|
|
|
### 134 GHz — Mini Window
|
|
|
|
**Primary mechanisms:** Enhanced LOS
|
|
**Key variable:** Between O2 118 and H2O 183 lines
|
|
**Best conditions:** Cold, dry, no precipitation
|
|
**Unique:** 157 km record (Germany, March). Better than 122 GHz due to distance from O2 line.
|
|
|
|
### 241 GHz (1.2mm) — Submillimeter
|
|
|
|
**Primary mechanisms:** LOS only
|
|
**Key variable:** H2O absorption dominates (~0.3 dB/km per g/m^3)
|
|
**Best conditions:** Extremely dry (<3 g/m^3), winter-only in most US locations, high altitude stations
|
|
**Unique:** 114 km record (Virginia, January). Total path loss at 100 km is ~410 dB without ducting. Realistic to display "viable / not viable" rather than a score.
|
|
|
|
---
|
|
|
|
## Part 11: Data Flow & Implementation
|
|
|
|
```
|
|
Surface Observations (ASOS, every 5-20 min)
|
|
-> temp, dewpoint, wind, pressure, sky, visibility, wx_codes
|
|
-> compute: abs_humidity, Td depression
|
|
-> per-band scoring functions
|
|
-> composite score per band
|
|
-> 6-hour prediction timeline
|
|
|
|
HRRR Model (hourly, per grid point)
|
|
-> refractivity profile, dN/dh gradient, ducts, BL depth, PWAT, surface N
|
|
-> refractivity score component (10% weight — best discriminator)
|
|
-> 4,522 profiles in DB: 79% Enhanced, 15% Super, 5% Standard, 0.7% Ducting
|
|
-> Key thresholds: gradient < -300 = moderate ducting, < -500 = strong ducting
|
|
-> HPBL < 200m = strongest signal for enhanced propagation
|
|
|
|
Sounding Data (RAOB 00Z/12Z)
|
|
-> 3,901 soundings from 112 stations
|
|
-> Same derived params as HRRR but only twice daily
|
|
-> 54% show ducting — binary flag useless, gradient magnitude is the signal
|
|
-> K-index INVERSELY correlates with ducting (12.7 ducting vs 16.7 non-ducting)
|
|
-> PWAT is NOT a ducting discriminator (identical 28.0 mm both cases)
|
|
|
|
IEMRE Gridded Data (hourly, 0.125° resolution)
|
|
-> temp, dewpoint, sky_cover_pct, wind (u/v), precip at QSO endpoint locations
|
|
-> More granular than nearest-ASOS matching
|
|
-> 3,675 gridded observations in DB, enriched per-QSO
|
|
|
|
Terrain Data (SRTM)
|
|
-> path profile, Fresnel clearance, earth bulge
|
|
-> 97.2% of QSO paths are BLOCKED (avg 36.2 dB diffraction loss)
|
|
-> 2.2% CLEAR, 0.6% FRESNEL_PARTIAL
|
|
-> Blocked paths average LONGER distances (215 km) than clear paths (84 km)
|
|
-> Determines LOS vs beyond-LOS regime
|
|
-> diffraction loss calculation
|
|
|
|
Commercial Link Data (SNMP polling, 5-min intervals)
|
|
-> 7 links at 11/24/68 GHz near DFW
|
|
-> rx_power_0, rx_power_1 (dual-chain MIMO on af11x), tx_power
|
|
-> Signal variation scales with frequency: 68G > 24G > 11G
|
|
-> Correlated with KTKI ASOS surface obs
|
|
|
|
Link Budget (point-to-point)
|
|
-> FSPL + gaseous + rain + diffraction - duct enhancement
|
|
-> margin = RX power - sensitivity
|
|
-> success % = margin_to_success(margin, prop_score)
|
|
-> Note: 36 dB avg diffraction > 14 dB max duct enhancement
|
|
(gap closed by station EIRP + receiver sensitivity + troposcatter)
|
|
```
|
|
|
|
### Display: Band Conditions Panel
|
|
|
|
For each band:
|
|
- Current score (0-100, colored badge)
|
|
- Estimated range (km, from score tier table, qualified by mode)
|
|
- Key limiting factor ("High humidity: 16 g/m^3", "Strong inversion detected")
|
|
- Trend arrow (improving/stable/degrading from last hour)
|
|
- 6-hour prediction timeline with confidence shading
|
|
|
|
---
|
|
|
|
## Constants Reference
|
|
|
|
| Constant | Value | Source |
|
|
|----------|-------|--------|
|
|
| Earth radius | 6371 km | WGS-84 mean |
|
|
| Standard K-factor | 4/3 | Standard atmosphere |
|
|
| Standard surface N | 315 | ITU-R P.453 |
|
|
| Standard dN/dh | -40 /km | ITU-R P.453 |
|
|
| Humidity penalty 24 GHz | 1.6 | Near 22.235 GHz H2O peak |
|
|
| Humidity penalty 47 GHz | 1.0 | Atmospheric window |
|
|
| Humidity penalty 68 GHz | 1.4 | 60 GHz O2 wing + H2O |
|
|
| Humidity penalty 241 GHz | 3.0 | Between H2O 183 & 325 |
|
|
| Duct M-unit threshold | 2 | Noise filter |
|
|
| Inversion min strength | 0.5C | Below is noise |
|
|
| Inversion height limit | 5000m AGL | Above irrelevant |
|
|
| BL depth shallow threshold | <300m | HRRR: avg gradient -93.7 at BL<200m |
|
|
| Ducting gradient threshold | -300 N/km | Sounding avg for ducting events: -389 |
|
|
| Non-ducting gradient avg | -123 N/km | Sounding avg for non-ducting events |
|
|
| Ducting surface N threshold | 330 | Above this, ducting probability >50% |
|
|
| Signal prediction floor | +/- 2-3 dB | Measured from link data |
|
|
| CW bandwidth advantage | ~7 dB | 10*log10(2700/500); 24% range increase |
|
|
|
|
## ITU-R References
|
|
|
|
- P.453-14: Radio refractivity
|
|
- P.525: Free-space path loss
|
|
- P.676-13: Gaseous absorption (O2 + H2O)
|
|
- P.838-3: Specific rain attenuation
|
|
- P.526: Diffraction
|
|
- P.452: Interference between stations
|
|
|
|
## Data References
|
|
|
|
- ARRL Microwave Contest QSO database: 58,282 total, 57,488 tropospheric with distance (1992-2024)
|
|
- IEM ASOS observations: 1,299 stations, 58,398 surface observations
|
|
- IEM RAOB soundings: 112 stations, 3,901 soundings
|
|
- IEMRE gridded hourly: 3,675 observations at 0.125° resolution
|
|
- HRRR model profiles: 4,522 profiles with refractivity, BL depth, PWAT
|
|
- Terrain profiles: 58,276 QSO paths (97.2% blocked, avg 36.2 dB diffraction)
|
|
- Commercial link data: 7 links at 11/24/68 GHz near DFW, SNMP-polled
|
|
- Solar indices: 9,586 daily values (1998-2026) — reserved for future use
|
|
|
|
## Known Data Quality Issues
|
|
|
|
- **EME contamination**: 4 QSOs >3,000 km remain in dataset (QRA64D/JT4F modes). Filter on `distance_km < 3000` for tropospheric analysis.
|
|
- **Unmodeled bands**: 142, 145, 288, 322, 403, 411 GHz have 1-4 QSOs each but no band_config entries. Too sparse for statistical analysis.
|
|
- **Sounding data recency**: Latest soundings are from Sep 2024. Ingestion pipeline may need restart for live enrichment.
|
|
- **Surface obs density**: Historical obs are sparse (~1 per 4.7 days per station) because they were fetched per-QSO-window. Live polling is now active for continuous coverage.
|
|
- **Weather codes (wx_codes)**: Stored in surface_observations but unused by scoring. Direct fog/thunderstorm/freezing-rain detection could supplement indirect inference from Td depression and sky condition.
|