defmodule MicrowavepropWeb.SkewTPropertyTest do @moduledoc """ StreamData property tests for `MicrowavepropWeb.SkewT`'s atmospheric math helpers. Invariants covered: * Magnus saturation vapor pressure is positive and strictly monotonic in temperature. * `temperature_from_mixing_ratio/2` is the inverse of the ws = 622·es / (p − es) forward relation. * Along a dry adiabat, temperature decreases monotonically as pressure falls (lift a parcel → it cools). * At the 1000 mb reference pressure, dry-adiabat T equals θ − 273.15 (definition of potential temperature). * `project/3` reproduces the chart corners and preserves pressure ordering on the y-axis. """ use ExUnit.Case, async: true use ExUnitProperties alias MicrowavepropWeb.SkewT describe "saturation_vapor_pressure/1" do property "is strictly positive across the troposphere" do check all(t_c <- float(min: -80.0, max: 50.0)) do assert SkewT.saturation_vapor_pressure(t_c) > 0 end end property "is strictly monotonic in temperature" do check all( t_c <- float(min: -80.0, max: 50.0), delta <- float(min: 0.001, max: 10.0) ) do lo = SkewT.saturation_vapor_pressure(t_c) hi = SkewT.saturation_vapor_pressure(t_c + delta) assert hi > lo end end end describe "temperature_from_mixing_ratio/2" do property "round-trips T → ws → T for realistic (T, p) pairs" do check all( t_c <- float(min: -30.0, max: 35.0), p_mb <- float(min: 300.0, max: 1050.0) ) do es = SkewT.saturation_vapor_pressure(t_c) # Only test where ws is well-defined (p > es). if p_mb > es + 1.0 do ws_g_kg = 622.0 * es / (p_mb - es) recovered = SkewT.temperature_from_mixing_ratio(ws_g_kg, p_mb) assert_in_delta recovered, t_c, 0.01 end end end end describe "dry_adiabat_temperature/2" do property "equals θ − 273.15 at the 1000 mb reference pressure" do check all(theta_k <- float(min: 200.0, max: 400.0)) do t_c = SkewT.dry_adiabat_temperature(theta_k, 1000.0) assert_in_delta t_c, theta_k - 273.15, 1.0e-6 end end property "temperature decreases as pressure decreases along an adiabat" do check all( theta_k <- float(min: 250.0, max: 360.0), p_hi <- float(min: 500.0, max: 1050.0), drop <- float(min: 10.0, max: 400.0) ) do p_lo = p_hi - drop if p_lo > 50.0 do t_hi = SkewT.dry_adiabat_temperature(theta_k, p_hi) t_lo = SkewT.dry_adiabat_temperature(theta_k, p_lo) assert t_hi > t_lo end end end end describe "project/3" do setup do %{chart: SkewT.chart_config([])} end property "projects p_bot to the plot bottom and p_top to the plot top", %{chart: chart} do check all(t_c <- float(min: chart.t_min, max: chart.t_max)) do {_x_bot, y_bot} = SkewT.project(t_c, chart.p_bot, chart) {_x_top, y_top} = SkewT.project(t_c, chart.p_top, chart) # SVG y grows downward: the bottom of the chart has a larger y. assert y_bot > y_top assert_in_delta y_bot, chart.padding_top + chart.plot_height, 1.0e-6 assert_in_delta y_top, chart.padding_top, 1.0e-6 end end property "pressure ordering is inverted on the y-axis (higher p → larger y)", %{chart: chart} do check all( p_hi <- float(min: 200.0, max: 1050.0), p_lo <- float(min: 100.0, max: 1000.0), t_c <- float(min: -20.0, max: 20.0) ) do if p_hi > p_lo + 1.0 do {_x1, y_hi} = SkewT.project(t_c, p_hi, chart) {_x2, y_lo} = SkewT.project(t_c, p_lo, chart) assert y_hi > y_lo end end end end end