defmodule Microwaveprop.Propagation.InversionTest do use ExUnit.Case, async: true alias Microwaveprop.Propagation.Inversion # Helper: build a profile struct-like map from level tuples. # Each level is {height_m, temp_k, spfh, pressure_pa, u_ms, v_ms, tke} defp profile(levels) do %{ level_count: length(levels), heights_m: Enum.map(levels, &elem(&1, 0)), temp_k: Enum.map(levels, &elem(&1, 1)), spfh: Enum.map(levels, &elem(&1, 2)), pressure_pa: Enum.map(levels, &elem(&1, 3)), u_wind_ms: Enum.map(levels, &elem(&1, 4)), v_wind_ms: Enum.map(levels, &elem(&1, 5)), tke_m2s2: Enum.map(levels, &elem(&1, 6)) } end describe "find_inversion_top/1" do test "returns :none for a monotonically cooling profile (no inversion)" do # Standard atmosphere: T decreases with height, no inversion p = profile([ {10.0, 295.0, 0.010, 101_000.0, 2.0, 1.0, 0.5}, {100.0, 294.0, 0.009, 100_000.0, 2.5, 1.0, 0.4}, {300.0, 292.0, 0.007, 97_000.0, 3.0, 1.5, 0.3}, {500.0, 289.0, 0.005, 95_000.0, 4.0, 2.0, 0.2}, {1000.0, 283.0, 0.003, 90_000.0, 5.0, 2.5, 0.1} ]) assert Inversion.find_inversion_top(p) == :none end test "detects a surface-based temperature inversion" do # Cool at surface, warm layer at 100-300m (classic radiation inversion) p = profile([ {10.0, 288.0, 0.008, 101_000.0, 1.0, 0.5, 0.1}, {50.0, 289.0, 0.008, 100_500.0, 1.0, 0.5, 0.1}, {100.0, 291.0, 0.007, 100_000.0, 1.5, 0.5, 0.1}, {200.0, 293.0, 0.006, 99_000.0, 2.0, 1.0, 0.2}, {300.0, 292.0, 0.006, 98_000.0, 3.0, 1.5, 0.3}, {500.0, 289.0, 0.005, 95_000.0, 4.0, 2.0, 0.5} ]) {:ok, top} = Inversion.find_inversion_top(p) # The inversion top is where temperature stops increasing and starts decreasing # Between 200m (293K) and 300m (292K) — so the top is at the 200m level assert top.height_m == 200.0 assert top.level_idx == 3 assert top.strength_k > 0 end test "detects an elevated inversion" do # Normal lapse below 500m, inversion between 500-1000m p = profile([ {10.0, 295.0, 0.010, 101_000.0, 2.0, 1.0, 0.5}, {100.0, 294.0, 0.009, 100_000.0, 2.5, 1.0, 0.4}, {300.0, 292.0, 0.007, 97_000.0, 3.0, 1.5, 0.3}, {500.0, 290.0, 0.005, 95_000.0, 4.0, 2.0, 0.2}, {700.0, 292.0, 0.004, 93_000.0, 5.0, 3.0, 0.1}, {1000.0, 294.0, 0.003, 90_000.0, 6.0, 3.5, 0.1}, {1500.0, 291.0, 0.002, 85_000.0, 8.0, 4.0, 0.2} ]) {:ok, top} = Inversion.find_inversion_top(p) # T increases from 500m to 1000m then drops — top at 1000m assert top.height_m == 1000.0 assert top.level_idx == 5 end test "returns :none for a profile with too few levels" do p = profile([{10.0, 295.0, 0.010, 101_000.0, 2.0, 1.0, 0.5}]) assert Inversion.find_inversion_top(p) == :none end end describe "bulk_richardson/3" do test "returns large Ri for a strong inversion with weak shear (laminar)" do # Strong warming (5 K) with minimal wind change → Ri >> 1 p = profile([ {10.0, 288.0, 0.008, 101_000.0, 2.0, 1.0, 0.1}, {200.0, 293.0, 0.006, 99_000.0, 2.1, 1.1, 0.1} ]) ri = Inversion.bulk_richardson(p, 0, 1) assert ri > 1.0 end test "returns small Ri for an inversion with strong shear (turbulent)" do # Mild warming (1 K) with strong wind change (20 m/s) → Ri < 0.25 p = profile([ {10.0, 290.0, 0.008, 101_000.0, 2.0, 1.0, 0.1}, {200.0, 291.0, 0.006, 99_000.0, 22.0, 1.0, 0.5} ]) ri = Inversion.bulk_richardson(p, 0, 1) assert ri < 0.25 end test "clamps at 100.0 when shear is near zero" do p = profile([ {10.0, 290.0, 0.008, 101_000.0, 2.0, 1.0, 0.1}, {200.0, 295.0, 0.006, 99_000.0, 2.0, 1.0, 0.1} ]) ri = Inversion.bulk_richardson(p, 0, 1) assert ri == 100.0 end end describe "shear_magnitude/3" do test "computes the vector magnitude of wind difference" do p = profile([ {10.0, 290.0, 0.008, 101_000.0, 0.0, 0.0, 0.1}, {200.0, 290.0, 0.008, 101_000.0, 3.0, 4.0, 0.1} ]) shear = Inversion.shear_magnitude(p, 0, 1) assert_in_delta shear, 5.0, 0.001 end end describe "potential_temperature/2" do test "computes theta correctly at standard sea-level conditions" do # At P = 100000 Pa (exactly), theta should equal T theta = Inversion.potential_temperature(300.0, 100_000.0) assert_in_delta theta, 300.0, 0.01 end test "theta > T when pressure is above 1000 hPa" do # At P = 101325 Pa, theta should be slightly less than T theta = Inversion.potential_temperature(300.0, 101_325.0) assert theta < 300.0 end end end