defmodule Microwaveprop.Propagation.HfMufTest do use ExUnit.Case, async: true alias Microwaveprop.Propagation.HfMuf describe "adjust_mufd/2" do test "returns 0.0 for nil or non-positive inputs" do assert HfMuf.adjust_mufd(nil, 2000) == 0.0 assert HfMuf.adjust_mufd(23.0, nil) == 0.0 assert HfMuf.adjust_mufd(0.0, 2000) == 0.0 assert HfMuf.adjust_mufd(23.0, 0) == 0.0 end test "is a no-op at the reference distance (3000 km)" do # MUFD is *defined* at the 3000 km GIRO DIDBase reference distance. assert_in_delta HfMuf.adjust_mufd(23.0, 3000), 23.0, 0.001 end test "shorter paths have lower MUF (closer to vertical incidence)" do short = HfMuf.adjust_mufd(23.0, 1500) medium = HfMuf.adjust_mufd(23.0, 2500) ref = HfMuf.adjust_mufd(23.0, 3000) assert short < medium assert medium < ref end test "longer paths within single-hop window have higher MUF" do ref = HfMuf.adjust_mufd(23.0, 3000) long = HfMuf.adjust_mufd(23.0, 4000) very_long = HfMuf.adjust_mufd(23.0, 5000) assert long > ref assert very_long > long end test "vertical-incidence limit approaches foF2 (roughly 1/sec(i_ref) × MUFD)" do # As distance → 0 the ray is vertical and MUF → foF2. # For h=300, sec(i_ref) ≈ 5.10, so at D=0 the scaled MUFD should # be about MUFD / 5.10 ≈ foF2. tiny = HfMuf.adjust_mufd(23.0, 1) assert_in_delta tiny, 23.0 / 5.099, 0.1 end end describe "fot/1" do test "returns 0.85 × MUF" do assert HfMuf.fot(20.0) == 17.0 assert_in_delta HfMuf.fot(28.35), 24.1, 0.01 end test "returns 0.0 for nil or non-positive inputs" do assert HfMuf.fot(nil) == 0.0 assert HfMuf.fot(0.0) == 0.0 assert HfMuf.fot(-5.0) == 0.0 end end describe "hf_score/2" do test "returns 0 for nil or non-positive MUF" do assert HfMuf.hf_score(nil, 14.0) == 0 assert HfMuf.hf_score(0.0, 14.0) == 0 end test "returns 0 when band is well above MUF" do # 28 MHz on a path with MUF=15 MHz — 10m is dead. assert HfMuf.hf_score(15.0, 28.0) == 0 end test "returns 100 when band is comfortably below FOT" do # 7 MHz on a path with MUF=30 MHz — 40m is solid, way below FOT (25.5). assert HfMuf.hf_score(30.0, 7.0) == 100 end test "returns 80 near FOT (85% of MUF)" do # Band ≈ 0.85 × MUF should land on FOT bucket. assert HfMuf.hf_score(20.0, 17.0) == 80 end test "returns 50 between FOT and MUF" do # Band at 0.95 × MUF is marginal. assert HfMuf.hf_score(20.0, 19.0) == 50 end test "returns 20 in the fringe zone slightly above MUF" do # Band 10% above MUF — fading, spotty, but sometimes there. assert HfMuf.hf_score(20.0, 22.0) == 20 end test "is monotonic: higher MUF never decreases the score at a fixed band" do # 14 MHz across a range of MUFs. scores = Enum.map([10, 14, 16, 18, 25, 40], &HfMuf.hf_score(&1 * 1.0, 14.0)) # Each score must be ≥ the previous. Enum.reduce(scores, 0, fn s, prev -> assert s >= prev s end) end end end