defmodule MicrowavepropWeb.SkewTTest do use ExUnit.Case, async: true alias MicrowavepropWeb.SkewT describe "saturation_vapor_pressure/1 (Magnus)" do test "reference values are within 1% of textbook" do # At 0°C the Magnus formula gives ≈ 6.112 hPa. assert_in_delta SkewT.saturation_vapor_pressure(0.0), 6.112, 0.05 # At 20°C es ≈ 23.37 hPa. assert_in_delta SkewT.saturation_vapor_pressure(20.0), 23.37, 0.2 # At -20°C es ≈ 1.254 hPa. assert_in_delta SkewT.saturation_vapor_pressure(-20.0), 1.254, 0.05 end end describe "temperature_from_mixing_ratio/2" do test "round-trips mixing ratios" do # Pick a pressure and temperature, compute ws, invert, expect the original. p = 850.0 for t <- [-10.0, 0.0, 15.0, 25.0] do es = SkewT.saturation_vapor_pressure(t) ws_g_kg = 622.0 * es / (p - es) recovered = SkewT.temperature_from_mixing_ratio(ws_g_kg, p) assert_in_delta recovered, t, 0.05 end end end describe "dry_adiabat_temperature/2" do test "θ == T at 1000 mb" do # At the reference pressure the potential temperature equals the # actual temperature — so θ = 300 K ⇒ T(1000) = 300 - 273.15 ≈ 26.85°C. assert_in_delta SkewT.dry_adiabat_temperature(300.0, 1000.0), 26.85, 0.02 end test "a parcel lifted dry-adiabatically cools" do # Lifting from 1000 mb (θ = 290 K, T ≈ 16.85°C) to 700 mb should # drop the temperature by ~26°C (roughly 9.8°C/km, ~3 km). t_surface = SkewT.dry_adiabat_temperature(290.0, 1000.0) t_700 = SkewT.dry_adiabat_temperature(290.0, 700.0) assert t_surface - t_700 > 20.0 end end describe "project/3" do setup do chart = SkewT.chart_config(width: 400, height: 500) %{chart: chart} end test "p_bot lands at the chart bottom and p_top at the top", %{chart: chart} do {_x_bot, y_bot} = SkewT.project(0.0, chart.p_bot, chart) {_x_top, y_top} = SkewT.project(0.0, chart.p_top, chart) # SVG y grows downward, so p_bot (bottom) has a larger y than p_top. assert y_bot > y_top assert_in_delta y_bot, chart.padding_top + chart.plot_height, 0.5 assert_in_delta y_top, chart.padding_top, 0.5 end test "isotherms skew right as pressure decreases", %{chart: chart} do # Same temperature at two pressures should have a larger x at # the lower pressure (top of chart) because of the skew. {x_bot, _} = SkewT.project(0.0, chart.p_bot, chart) {x_top, _} = SkewT.project(0.0, chart.p_top, chart) assert x_top > x_bot end test "temperature range spans the bottom of the chart", %{chart: chart} do # At p_bot, t_min lands at the left edge of the data area. {x_min, _} = SkewT.project(chart.t_min, chart.p_bot, chart) {x_max, _} = SkewT.project(chart.t_max, chart.p_bot, chart) assert_in_delta x_min, chart.padding_left, 0.5 assert_in_delta x_max, chart.padding_left + chart.plot_width, 0.5 end end describe "build/2" do test "with no profile returns nil" do assert SkewT.build(nil) == nil assert SkewT.build([]) == nil end test "returns a map of rendering primitives for a valid profile" do profile = [ %{"pres" => 1000.0, "hght" => 100.0, "tmpc" => 25.0, "dwpc" => 18.0}, %{"pres" => 850.0, "hght" => 1500.0, "tmpc" => 15.0, "dwpc" => 10.0}, %{"pres" => 700.0, "hght" => 3100.0, "tmpc" => 5.0, "dwpc" => -2.0}, %{"pres" => 500.0, "hght" => 5600.0, "tmpc" => -15.0, "dwpc" => -25.0} ] chart = SkewT.build(profile) assert is_map(chart) assert chart.width > 0 assert chart.height > 0 # Background grid assert chart.isobars != [] assert chart.isotherms != [] assert chart.dry_adiabats != [] assert chart.mixing_ratio_lines != [] # Traces assert chart.t_points != [] assert chart.td_points != [] # The first plotted t_point should correspond to the highest pressure # (surface-most) profile entry. assert length(chart.t_points) == 4 end test "filters out profile levels with missing temperature or dewpoint independently" do profile = [ %{"pres" => 1000.0, "tmpc" => 25.0, "dwpc" => 18.0}, %{"pres" => 900.0, "tmpc" => nil, "dwpc" => 15.0}, %{"pres" => 850.0, "tmpc" => 15.0, "dwpc" => nil}, %{"pres" => 700.0, "tmpc" => 5.0, "dwpc" => -2.0} ] chart = SkewT.build(profile) # 3 levels have tmpc, 3 have dwpc — the traces are independent. assert length(chart.t_points) == 3 assert length(chart.td_points) == 3 end end end