From d49ec5d32edaca933935a7ad1845df81ed47ca3f Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 11 Apr 2026 13:11:38 -0500 Subject: [PATCH] Drop dead backtest features, document consolidated results Backtest on 11,431 native profiles (2026-04-11): - Drop duct_usable_10/24/47ghz (always 1.0, no discrimination) - Drop bulk_richardson (near-identical QSO vs baseline means) - Document all feature results with signal strength assessment - theta_e_jump is strongest native discriminator (44% lift) - best_duct_freq and duct_thickness show clear physical signal --- algo.md | 36 +++++++++++++++++++++++--- lib/microwaveprop/backtest/features.ex | 18 ++++++++++--- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/algo.md b/algo.md index a7467ba7..05a2d395 100644 --- a/algo.md +++ b/algo.md @@ -1958,9 +1958,10 @@ Features are grouped by data source and physical quantity: | shear_at_top | hrrr_native_profiles | Wind shear (m/s) at inversion top | | duct_thickness | hrrr_native_profiles | Max duct thickness (m) — larger ducts trap lower frequencies | | best_duct_freq | hrrr_native_profiles | Lowest trapped frequency (GHz) — lower = stronger ducting | -| duct_usable_10ghz | hrrr_native_profiles | Binary: any duct traps 10 GHz? | -| duct_usable_24ghz | hrrr_native_profiles | Binary: any duct traps 24 GHz? | -| duct_usable_47ghz | hrrr_native_profiles | Binary: any duct traps 47 GHz? | +| ~~duct_usable_10ghz~~ | hrrr_native_profiles | DEAD — no discrimination (always 1.0 for both QSO and baseline) | +| ~~duct_usable_24ghz~~ | hrrr_native_profiles | DEAD — no discrimination (always 1.0) | +| ~~duct_usable_47ghz~~ | hrrr_native_profiles | DEAD — no discrimination (always 1.0) | +| ~~bulk_richardson~~ | hrrr_native_profiles | DEAD — near-identical means (24.8 vs 23.4), no lift | **Climatology and remote sensing:** @@ -1976,6 +1977,35 @@ Features are grouped by data source and physical quantity: | distance_to_front | — | Distance (km) to nearest detected front — awaiting frontal analysis pipeline | | parallel_to_front | — | cos²(path-front angle) — awaiting frontal analysis + requires path bearing | +### Consolidated Backtest Results (2026-04-11) + +Sample: 5,000 QSOs, 11,431 native profiles across 499 HRRR hours (2019-2024). + +| Feature | QSO N | QSO Mean | QSO p50 | Baseline Mean | Baseline p50 | Signal | +|---|---|---|---|---|---|---| +| **theta_e_jump** | 4915 | 49.4 K | 5.8 K | 34.3 K | 2.1 K | **Strong** — 44% higher jumps during QSOs | +| **best_duct_freq** | 697 | 0.84 GHz | 0.40 GHz | 0.28 GHz | 0.26 GHz | **Strong** — QSO ducts trap lower freqs | +| **native_surface_refractivity** | 4915 | 331.7 | 334.4 | 324.3 | 330.1 | **Moderate** — higher N during QSOs | +| **duct_thickness** | 697 | 156 m | 159 m | 227 m | 217 m | **Inverted** — thinner ducts during QSOs (shallow surface ducts) | +| **td_depression** | 5000 | 6.8°C | 5.8°C | 5.9°C | 3.8°C | **Moderate** — wider depression during QSOs | +| **time_of_day** | 5000 | 16.7h | 17.9h | 11.9h | 11.8h | **Strong** — contests are evening-biased | +| **naive_gradient** | 5000 | -113 | -104 | -107 | -97 | **Weak** — small separation | +| **shear_at_top** | 4915 | 6.4 m/s | 4.0 m/s | 5.8 m/s | 3.3 m/s | **Marginal** | +| **pressure** | 5000 | 983.8 | 989.5 | 982.7 | 989.7 | **Weak** — near-identical | +| **nexrad_texture** | 1796 | 16.8 | 0.0 | 20.6 | 0.0 | **Weak/inverted** | +| bulk_richardson | 4915 | 24.8 | 6.3 | 23.4 | 5.8 | **Dead** — no discrimination | +| duct_usable_*ghz | 697 | ~1.0 | 1.0 | ~1.0 | 1.0 | **Dead** — always 1.0 | +| temperature_anomaly | 0 | — | — | — | — | NO DATA (climatology not built) | + +**Key findings:** +- **theta_e_jump** is the single strongest native-level discriminator — large theta-e jumps at inversion tops indicate strong decoupling that traps microwave energy +- **best_duct_freq** confirms that QSO-producing ducts are physically stronger (trap lower frequencies) +- **duct_thickness** being inverted makes physical sense: shallow surface ducts (100-200m) produce the strongest trapping for microwave bands, while thick ducts (>200m) are weaker elevated features +- **duct_usable_*ghz** features are dead because nearly all detected ducts are thick enough to trap 10-47 GHz — the threshold is too low to discriminate +- **bulk_richardson** shows no signal, likely because both stable (high Ri) and unstable (low Ri) conditions can produce inversions; Ri alone doesn't predict duct quality + +**Implications for real-time scoring:** The native features cannot currently be used in real-time propagation scoring because the native HRRR product (~530 MB/hour) is too expensive to fetch for the full CONUS grid. However, the findings validate the existing scorer's physics: humidity, td_depression, and refractivity factors capture the same mechanisms (moisture-driven refractivity, inversion strength) that the native features measure more directly. Future work could incorporate native data for specific paths or high-interest regions. + ### HRRR Climatology The `hrrr_climatology` table stores pre-computed mean and standard deviation of surface temperature from `hrrr_profiles`, keyed on `(lat, lon, month, hour)` at the HRRR grid resolution. This allows computing how anomalous the current surface temperature is relative to historical norms for the same location, season, and time of day. The meteorologist noted that extremely hot days (~10°F above normal in summer) produce enhanced ducting even in the afternoon when the time-of-day factor normally suppresses the score. The `temperature_anomaly` feature returns `surface_temp_c - climatological_mean` as a signed float. diff --git a/lib/microwaveprop/backtest/features.ex b/lib/microwaveprop/backtest/features.ex index 33a992c5..640d4896 100644 --- a/lib/microwaveprop/backtest/features.ex +++ b/lib/microwaveprop/backtest/features.ex @@ -29,11 +29,23 @@ defmodule Microwaveprop.Backtest.Features do @doc """ Returns a map of all backtestable features: `%{name => fun/3}`. - Excludes `duct_usable_for_band/4` (4-arity) and the placeholder stubs - that always return nil (`distance_to_front`, `parallel_to_front`). + Excludes dead features (no discrimination in backtest), 4-arity helpers, + and placeholder stubs that always return nil. """ def all_features do - excluded = MapSet.new([:duct_usable_for_band, :distance_to_front, :parallel_to_front]) + excluded = + MapSet.new([ + # 4-arity helper + :duct_usable_for_band, + # Stubs (always nil) + :distance_to_front, + :parallel_to_front, + # Dead features — no discrimination in consolidated backtest (2026-04-11) + :duct_usable_10ghz, + :duct_usable_24ghz, + :duct_usable_47ghz, + :bulk_richardson + ]) :functions |> __MODULE__.__info__()