60 lines
2.8 KiB
Python
60 lines
2.8 KiB
Python
import numpy as np
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from damage_engine.sources import derived
|
|
|
|
# real hours of station 566 (2026-10-19): hourly mean T (°C), mean RH (%), 3A's TBulbMed (°C)
|
|
STATION_566 = [(13.3, 99, 13.2), (13.8, 90, 12.8), (15.6, 82, 13.8),
|
|
(15.8, 78, 13.6), (15.9, 76, 13.4), (15.2, 78, 13.0)]
|
|
|
|
|
|
@pytest.mark.parametrize("t, rh, tbulb_3a", STATION_566)
|
|
def test_formula_reproduces_3a_tbulbmed(t, rh, tbulb_3a):
|
|
# same formula as 3A (document section 4): equal to 3A's value at its 0.1 °C rounding
|
|
assert abs(float(derived.wet_bulb_c(t, rh)) - tbulb_3a) <= 0.05
|
|
|
|
|
|
def test_saturated_air_wet_bulb_equals_air_temperature():
|
|
for t in (-5.0, 0.0, 10.0):
|
|
assert abs(float(derived.wet_bulb_c(t, 100)) - t) < 1e-6
|
|
|
|
|
|
def test_wet_bulb_below_air_when_dry_and_ice_branch_below_zero():
|
|
assert float(derived.wet_bulb_c(20, 50)) < 20
|
|
assert float(derived.wet_bulb_c(-3, 80)) < -3
|
|
# T <= 0 uses the ice constants: the result is continuous enough across 0 °C
|
|
assert abs(float(derived.wet_bulb_c(0.0, 90)) - float(derived.wet_bulb_c(0.01, 90))) < 0.2
|
|
|
|
|
|
def test_dew_point_at_saturation_is_air_temperature():
|
|
assert abs(float(derived.dew_point_c(12.0, 100)) - 12.0) < 1e-6
|
|
|
|
|
|
def test_add_wet_bulb_pairs_tmin_rhmax_and_tmax_rhmin():
|
|
df = pd.DataFrame({derived.T_MIN: [14.6], derived.T_MEAN: [15.6], derived.T_MAX: [17.5],
|
|
derived.RH_MIN: [70.0], derived.RH_MEAN: [82.0], derived.RH_MAX: [90.0]})
|
|
out = derived.add_wet_bulb(df)
|
|
assert out[derived.WET_BULB_MIN][0] == round(float(derived.wet_bulb_c(14.6, 90)), 2)
|
|
assert out[derived.WET_BULB_MAX][0] == round(float(derived.wet_bulb_c(17.5, 70)), 2)
|
|
assert out[derived.WET_BULB_MIN][0] <= out[derived.WET_BULB_MAX][0]
|
|
assert derived.WET_BULB_MIN not in df # input not changed
|
|
|
|
|
|
def test_add_wet_bulb_falls_back_to_means_and_never_fails():
|
|
df = pd.DataFrame({derived.T_MEAN: [5.0, None], derived.RH_MEAN: [90.0, 80.0]})
|
|
out = derived.add_wet_bulb(df) # no min/max columns at all
|
|
expected = round(float(derived.wet_bulb_c(5.0, 90)), 2)
|
|
assert out[derived.WET_BULB_MIN][0] == expected == out[derived.WET_BULB_MAX][0]
|
|
assert np.isnan(out[derived.WET_BULB_MIN][1]) # no temperature -> NaN
|
|
assert derived.add_wet_bulb(pd.DataFrame({"x": [1]}))[derived.WET_BULB_MIN].isna().all()
|
|
|
|
|
|
def test_frost_point_below_zero_uses_ice_constants():
|
|
assert abs(float(derived.dew_point_c(0, 100))) < 1e-9
|
|
assert float(derived.dew_point_c(-3, 40)) < -10
|
|
# T <= 0 °C: e = RH * 6.1 * 10^(9.5 T / (T + 265.5)), inverted with the same constants
|
|
e = 0.95 * 6.1 * 10 ** (9.5 * -3 / (-3 + 265.5))
|
|
x = np.log10(e / 6.1)
|
|
assert abs(float(derived.dew_point_c(-3, 95)) - 265.5 * x / (9.5 - x)) < 1e-9
|