From c9112b928075307ee2d79e004517f61cabe72d6f Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 31 Mar 2026 16:08:15 -0500 Subject: [PATCH] Recalibrate refractivity thresholds for HRRR gradient distribution Previous thresholds (-500 to -60) were calibrated for radiosonde data. HRRR profiles have coarser vertical resolution, with gradients clustering between -40 and -130 N/km (median -70). Nearly all grid points were falling through to the default score of 42, wasting the refractivity factor. New thresholds (-200 to -40) spread across HRRR percentiles. --- algo.md | 51 +++++-------------- lib/microwaveprop/propagation/band_config.ex | 13 +++-- .../propagation/band_config_test.exs | 11 ++-- .../microwaveprop/propagation/scorer_test.exs | 10 ++-- 4 files changed, 31 insertions(+), 54 deletions(-) diff --git a/algo.md b/algo.md index cc667be4..97edf2f9 100644 --- a/algo.md +++ b/algo.md @@ -685,48 +685,21 @@ 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. +Best predictor when available. Binary duct detection is useless (54% baseline rate). Use continuous gradient magnitude and BL depth instead. -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). +Thresholds calibrated for HRRR-derived gradients which are coarser than radiosonde soundings. HRRR gradient distribution: p1=-230, p5=-162, p10=-130, p25=-94, p50=-70, p75=-53, p95=-40 N/km. Previous thresholds (-500 to -60) placed nearly all HRRR profiles in the default bucket. -```elixir -def score_refractivity(sounding_or_hrrr, band_config) do - cond do - sounding_or_hrrr == nil -> 50 # No data — neutral +| Gradient (N/km) | Beneficial Score | Harmful Score | Condition | +|---|---|---|---| +| < -200 | 98 | 85 | Strong ducting (HRRR p1) | +| < -150 | 92 | 80 | Enhanced refraction (HRRR p5) | +| < -100 | 82 | 72 | Above-average refraction (HRRR p25) | +| < -75 | 68 | 62 | Near-median gradient (HRRR p50) | +| < -55 | 55 | 55 | Below-median (HRRR p75) | +| < -40 | 48 | 48 | Weak gradient (HRRR p95) | +| ≥ -40 | 42 | 42 | Standard/sub-refractive | - 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 -``` +Shallow BL fallback: when gradient is unavailable but BL depth < 300m, score 82 (strong inversion cap). --- diff --git a/lib/microwaveprop/propagation/band_config.ex b/lib/microwaveprop/propagation/band_config.ex index d839f892..d2e25421 100644 --- a/lib/microwaveprop/propagation/band_config.ex +++ b/lib/microwaveprop/propagation/band_config.ex @@ -32,12 +32,15 @@ defmodule Microwaveprop.Propagation.BandConfig do @humidity_beneficial_thresholds [{4, 55}, {7, 70}, {10, 82}, {14, 90}, {18, 95}, {22, 88}] @humidity_beneficial_default 75 + # Thresholds calibrated for HRRR-derived gradients (coarser than sounding data). + # HRRR percentiles: p1=-230, p5=-162, p10=-130, p25=-94, p50=-70, p75=-53, p95=-40 @refractivity_thresholds [ - {-500, 98, 85}, - {-300, 92, 78}, - {-200, 80, 80}, - {-100, 65, 65}, - {-60, 55, 55} + {-200, 98, 85}, + {-150, 92, 80}, + {-100, 82, 72}, + {-75, 68, 62}, + {-55, 55, 55}, + {-40, 48, 48} ] @refractivity_default {42, 42} @shallow_bl_threshold_m 300 diff --git a/test/microwaveprop/propagation/band_config_test.exs b/test/microwaveprop/propagation/band_config_test.exs index 2b48ba51..bfff3d5d 100644 --- a/test/microwaveprop/propagation/band_config_test.exs +++ b/test/microwaveprop/propagation/band_config_test.exs @@ -252,11 +252,12 @@ defmodule Microwaveprop.Propagation.BandConfigTest do thresholds = BandConfig.refractivity_thresholds() assert thresholds == [ - {-500, 98, 85}, - {-300, 92, 78}, - {-200, 80, 80}, - {-100, 65, 65}, - {-60, 55, 55} + {-200, 98, 85}, + {-150, 92, 80}, + {-100, 82, 72}, + {-75, 68, 62}, + {-55, 55, 55}, + {-40, 48, 48} ] end end diff --git a/test/microwaveprop/propagation/scorer_test.exs b/test/microwaveprop/propagation/scorer_test.exs index 00c40602..06d74632 100644 --- a/test/microwaveprop/propagation/scorer_test.exs +++ b/test/microwaveprop/propagation/scorer_test.exs @@ -227,14 +227,14 @@ defmodule Microwaveprop.Propagation.ScorerTest do assert Scorer.score_refractivity(-600, 500, @band_10g) == 98 end - test "strong ducting gradient returns 85 for harmful" do - # gradient < -500 -> 85 for harmful - assert Scorer.score_refractivity(-600, 500, @band_24g) == 85 + test "strong gradient returns 85 for harmful" do + # gradient < -200 -> 85 for harmful (HRRR-calibrated) + assert Scorer.score_refractivity(-250, 500, @band_24g) == 85 end test "moderate gradient returns appropriate score" do - # gradient < -300 but >= -500 -> 92 for beneficial - assert Scorer.score_refractivity(-400, 500, @band_10g) == 92 + # gradient < -150 but >= -200 -> 92 for beneficial (HRRR-calibrated) + assert Scorer.score_refractivity(-160, 500, @band_10g) == 92 end test "nil gradient returns 50" do