Expands submittable-contact bands to include 6m, 2m, 1.25m, and 70cm (as 432 rather than the old 440 placeholder). Each new band gets an explicit allocation window in BandResolver.nearest_band so ADIF FREQ fields near the amateur allocations resolve correctly while 60-900 MHz frequencies outside those windows are still rejected. Microwave (>= 900 MHz) snapping is unchanged — nearest-band match across the full @allowed_bands list. Also adds BandConfig entries for 50 and 222 (tropo-only config, same pattern as 144/432). Sporadic-E / F2 / meteor scatter modeling is not yet in scope — ionosphere data is only used to compute an Es readout on /path for 50/144/222/432.
86 lines
3.5 KiB
Elixir
86 lines
3.5 KiB
Elixir
defmodule Microwaveprop.Propagation.SporadicETest do
|
||
use ExUnit.Case, async: true
|
||
|
||
alias Microwaveprop.Propagation.SporadicE
|
||
|
||
describe "single_hop_muf/2" do
|
||
test "returns 0.0 for nil or non-positive inputs" do
|
||
assert SporadicE.single_hop_muf(nil, 2000) == 0.0
|
||
assert SporadicE.single_hop_muf(5.0, nil) == 0.0
|
||
assert SporadicE.single_hop_muf(0.0, 2000) == 0.0
|
||
assert SporadicE.single_hop_muf(5.0, 0) == 0.0
|
||
assert SporadicE.single_hop_muf(-1.0, 2000) == 0.0
|
||
end
|
||
|
||
test "matches the thin-layer secant-of-incidence formula for known distances" do
|
||
# h = 110 km. sec(i) = sqrt(1 + (D/(2h))^2). Values cross-checked
|
||
# against literature (Davies 1990, Ionospheric Radio Eq. 6.26).
|
||
# At D=2000 km, sec(i) ≈ 9.146, so foEs × 9.146 MHz.
|
||
assert_in_delta SporadicE.single_hop_muf(10.0, 2000), 10.0 * 9.146, 0.05
|
||
assert_in_delta SporadicE.single_hop_muf(5.0, 1000), 5.0 * 4.654, 0.05
|
||
assert_in_delta SporadicE.single_hop_muf(8.0, 1500), 8.0 * 6.891, 0.05
|
||
end
|
||
|
||
test "degenerate short range approaches foEs (vertical incidence)" do
|
||
# As D → 0 the ray is nearly vertical and MUF → foEs.
|
||
assert_in_delta SporadicE.single_hop_muf(5.0, 1), 5.0, 0.001
|
||
end
|
||
end
|
||
|
||
describe "es_score/3" do
|
||
test "is 0 when foEs is nil or zero" do
|
||
assert SporadicE.es_score(nil, 144, 2000) == 0
|
||
assert SporadicE.es_score(0.0, 144, 2000) == 0
|
||
end
|
||
|
||
test "is 0 for paths shorter than the single-hop minimum" do
|
||
# Es geometry doesn't support paths much under ~500 km (the
|
||
# elevation angle is too steep for the layer to reflect a VHF
|
||
# signal regardless of foEs).
|
||
assert SporadicE.es_score(20.0, 144, 100) == 0
|
||
assert SporadicE.es_score(20.0, 144, 400) == 0
|
||
end
|
||
|
||
test "is 0 for paths beyond the single-hop maximum (~2500 km)" do
|
||
# Beyond ~2500 km the geometry exits the single-hop window. We
|
||
# don't model multi-hop Es here — that's a different factor.
|
||
assert SporadicE.es_score(20.0, 144, 3000) == 0
|
||
end
|
||
|
||
test "scores 50 MHz as ROUTINE when foEs is typical summer Es (6-8 MHz) at 2000 km" do
|
||
# foEs 6 MHz × sec(i) at 2000 km ≈ 54.9 MHz, just above 50 MHz.
|
||
# 50 MHz Es at 2000 km with foEs=6 is a classic summer opening —
|
||
# should score high.
|
||
score = SporadicE.es_score(6.0, 50, 2000)
|
||
assert score >= 80
|
||
end
|
||
|
||
test "scores 144 MHz as NONE when foEs is typical summer Es" do
|
||
# foEs=6 at 2000 km ≈ 54.9 MHz MUF — well below 144 MHz.
|
||
# Typical summer Es doesn't propagate 2m.
|
||
assert SporadicE.es_score(6.0, 144, 2000) == 0
|
||
end
|
||
|
||
test "scores 144 MHz as VIABLE when foEs reaches intense-Es levels (16+ MHz) at 2000 km" do
|
||
# foEs=16 × 9.15 ≈ 146 MHz MUF → 2m Es is feasible. These are
|
||
# rare but documented events (June/July peaks).
|
||
score = SporadicE.es_score(16.0, 144, 2000)
|
||
assert score >= 50
|
||
end
|
||
|
||
test "scores 432 MHz as NONE even at extreme foEs (sporadic-E does not reach 70cm)" do
|
||
# foEs would need to be ~47 MHz to get 432 MHz via single-hop Es
|
||
# — way beyond any physically observed value.
|
||
assert SporadicE.es_score(25.0, 432, 2000) == 0
|
||
end
|
||
|
||
test "higher foEs strictly increases the score at a given band/distance (monotonic)" do
|
||
weak = SporadicE.es_score(10.0, 50, 2000)
|
||
mid = SporadicE.es_score(14.0, 50, 2000)
|
||
strong = SporadicE.es_score(20.0, 50, 2000)
|
||
assert weak <= mid
|
||
assert mid <= strong
|
||
assert strong == 100
|
||
end
|
||
end
|
||
end
|