Add 144 MHz and 440 MHz bands (tropo only)
Both bands share the physics of low microwave: humidity and refractivity gradient build ducts the same way, and rain + gas absorption are effectively zero at VHF/UHF. humidity_effect is :beneficial, rain_k / rain_alpha are (0, 1), and o2_db_km / h2o_coeff are 0. Range estimates reflect tropo reality: 2m ducts can reach 2500+ km under exceptional conditions (e.g. documented Hawaii ↔ California openings), 70cm a bit less. The seasonal base matches the low-microwave summer-peak curve. These configs do NOT capture sporadic-E, meteor scatter, or aurora — those need ionospheric data (GIRO ionosondes, NOAA SWPC) we don't yet ingest. The scoring on these bands is therefore "tropo-only" and will underestimate openings driven by ionospheric modes.
This commit is contained in:
parent
1d0aa2848e
commit
91c8cc9bc7
3 changed files with 102 additions and 5 deletions
|
|
@ -48,6 +48,70 @@ defmodule Microwaveprop.Propagation.BandConfig do
|
|||
@refractivity_default {42, 42}
|
||||
|
||||
@band_configs %{
|
||||
# VHF / UHF bands share the tropo-ducting physics of low microwave
|
||||
# (humidity and refractivity gradient build ducts the same way),
|
||||
# so rain attenuation and gas absorption are effectively zero and
|
||||
# humidity is :beneficial. The difference from 902+ is mostly in
|
||||
# the achievable range — a 2m duct can reach 2500+ km under
|
||||
# exceptional conditions (e.g. documented Hawaii ↔ California
|
||||
# openings). These do NOT capture sporadic-E or meteor scatter,
|
||||
# which need ionospheric data we don't yet ingest.
|
||||
144 => %{
|
||||
freq_mhz: 144,
|
||||
label: "144 MHz",
|
||||
o2_db_km: 0.0,
|
||||
h2o_coeff: 0.0,
|
||||
humidity_effect: :beneficial,
|
||||
humidity_penalty: 0.0,
|
||||
rain_k: 0.0,
|
||||
rain_alpha: 1.0,
|
||||
seasonal_base: %{
|
||||
1 => 38,
|
||||
2 => 40,
|
||||
3 => 22,
|
||||
4 => 55,
|
||||
5 => 68,
|
||||
6 => 90,
|
||||
7 => 95,
|
||||
8 => 75,
|
||||
9 => 78,
|
||||
10 => 88,
|
||||
11 => 78,
|
||||
12 => 25
|
||||
},
|
||||
seasonal_adj: %{},
|
||||
typical_range_km: 350,
|
||||
extended_range_km: 900,
|
||||
exceptional_range_km: 2500
|
||||
},
|
||||
440 => %{
|
||||
freq_mhz: 440,
|
||||
label: "440 MHz",
|
||||
o2_db_km: 0.0,
|
||||
h2o_coeff: 0.0,
|
||||
humidity_effect: :beneficial,
|
||||
humidity_penalty: 0.0,
|
||||
rain_k: 0.0,
|
||||
rain_alpha: 1.0,
|
||||
seasonal_base: %{
|
||||
1 => 38,
|
||||
2 => 40,
|
||||
3 => 22,
|
||||
4 => 55,
|
||||
5 => 68,
|
||||
6 => 90,
|
||||
7 => 95,
|
||||
8 => 75,
|
||||
9 => 78,
|
||||
10 => 88,
|
||||
11 => 78,
|
||||
12 => 25
|
||||
},
|
||||
seasonal_adj: %{},
|
||||
typical_range_km: 300,
|
||||
extended_range_km: 700,
|
||||
exceptional_range_km: 1800
|
||||
},
|
||||
902 => %{
|
||||
freq_mhz: 902,
|
||||
label: "902 MHz",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ defmodule Microwaveprop.Propagation.BandConfigTest do
|
|||
alias Microwaveprop.Propagation.BandConfig
|
||||
|
||||
@all_freqs [
|
||||
144,
|
||||
440,
|
||||
902,
|
||||
1_296,
|
||||
2_304,
|
||||
|
|
@ -116,6 +118,37 @@ defmodule Microwaveprop.Propagation.BandConfigTest do
|
|||
assert BandConfig.get(999) == nil
|
||||
end
|
||||
|
||||
test "returns config for 144 MHz (2m VHF)" do
|
||||
config = BandConfig.get(144)
|
||||
assert config.freq_mhz == 144
|
||||
assert config.label == "144 MHz"
|
||||
# Tropo ducting physics is the same as low microwave — humidity
|
||||
# builds refractivity, rain and atmospheric absorption are
|
||||
# negligible at VHF.
|
||||
assert config.humidity_effect == :beneficial
|
||||
assert config.humidity_penalty == 0.0
|
||||
assert config.rain_k == 0.0
|
||||
assert config.rain_alpha == 1.0
|
||||
assert config.o2_db_km == 0.0
|
||||
assert config.h2o_coeff == 0.0
|
||||
# 2m tropo ducts can reach 2500+ km under exceptional conditions
|
||||
# (e.g. documented Hawaii ↔ California openings).
|
||||
assert config.exceptional_range_km >= 2000
|
||||
end
|
||||
|
||||
test "returns config for 440 MHz (70cm UHF)" do
|
||||
config = BandConfig.get(440)
|
||||
assert config.freq_mhz == 440
|
||||
assert config.label == "440 MHz"
|
||||
assert config.humidity_effect == :beneficial
|
||||
assert config.humidity_penalty == 0.0
|
||||
assert config.rain_k == 0.0
|
||||
assert config.rain_alpha == 1.0
|
||||
assert config.h2o_coeff == 0.0
|
||||
# 70cm tropo is strong but slightly less extreme than 2m.
|
||||
assert config.exceptional_range_km >= 1500
|
||||
end
|
||||
|
||||
test "returns config for 142 GHz (between H2O 118 and 183 lines)" do
|
||||
config = BandConfig.get(142_000)
|
||||
assert config.freq_mhz == 142_000
|
||||
|
|
@ -150,9 +183,9 @@ defmodule Microwaveprop.Propagation.BandConfigTest do
|
|||
end
|
||||
|
||||
describe "all_bands/0" do
|
||||
test "returns 19 bands (13 original + 142, 145, 288, 322, 403, 411 GHz)" do
|
||||
test "returns 21 bands (13 original + 142/145/288/322/403/411 GHz + 144/440 MHz)" do
|
||||
bands = BandConfig.all_bands()
|
||||
assert length(bands) == 19
|
||||
assert length(bands) == 21
|
||||
end
|
||||
|
||||
test "bands are sorted by frequency" do
|
||||
|
|
@ -328,7 +361,7 @@ defmodule Microwaveprop.Propagation.BandConfigTest do
|
|||
end
|
||||
|
||||
test "sub-10 GHz bands are beneficial, 24+ GHz are harmful" do
|
||||
beneficial = [902, 1_296, 2_304, 3_456, 5_760, 10_000]
|
||||
beneficial = [144, 440, 902, 1_296, 2_304, 3_456, 5_760, 10_000]
|
||||
harmful = @all_freqs -- beneficial
|
||||
|
||||
for freq <- beneficial do
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ defmodule Microwaveprop.PropagationTest do
|
|||
valid_time = ~U[2026-07-15 13:00:00Z]
|
||||
results = Propagation.score_grid_point(hrrr_profile, valid_time, 33.0, -97.0)
|
||||
|
||||
assert length(results) == 19
|
||||
assert length(results) == 21
|
||||
|
||||
Enum.each(results, fn result ->
|
||||
assert result.score >= 0 and result.score <= 100
|
||||
|
|
@ -98,7 +98,7 @@ defmodule Microwaveprop.PropagationTest do
|
|||
valid_time = ~U[2026-07-15 13:00:00Z]
|
||||
results = Propagation.score_grid_point(hrrr_profile_without_array, valid_time, 33.0, -97.0)
|
||||
|
||||
assert length(results) == 19
|
||||
assert length(results) == 21
|
||||
|
||||
Enum.each(results, fn result ->
|
||||
assert result.score >= 0 and result.score <= 100
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue