Skip to main content

pamoja_kit/
weather.rs

1//! Humidity-derived values: the dew point.
2
3use libm::log;
4
5/// Magnus coefficient (dimensionless), for the range about -45 to 60 C.
6const MAGNUS_B: f64 = 17.62;
7/// Magnus coefficient, in degrees Celsius.
8const MAGNUS_C: f64 = 243.12;
9
10/// Computes the dew point from temperature and relative humidity (Magnus formula).
11///
12/// The dew point is the temperature to which air must cool for its moisture to begin to
13/// condense; it is the practical signal behind condensation, fog, and frost. This uses the
14/// Magnus-Tetens approximation with the WMO coefficients (b = 17.62, c = 243.12 C), accurate
15/// from roughly -45 to 60 C: with `gamma = ln(rh / 100) + b * t / (c + t)`, the dew point is
16/// `c * gamma / (b - gamma)`. A dew point at or below 0 C means any condensation forms as
17/// frost, the basis of an overnight frost warning for a crop.
18///
19/// # Arguments
20///
21/// * `celsius` - the air temperature in degrees Celsius.
22/// * `humidity_percent` - the relative humidity in percent, in `(0, 100]`. A value at or
23///   below zero is treated as a tiny positive value so the logarithm stays defined.
24///
25/// # Returns
26///
27/// The dew point in degrees Celsius.
28///
29/// # Examples
30///
31/// ```
32/// use pamoja_kit::weather::dew_point;
33///
34/// // 20 C air at 50% relative humidity dews near 9.3 C.
35/// assert!((dew_point(20.0, 50.0) - 9.3).abs() < 0.2);
36///
37/// // Saturated air: the dew point equals the temperature.
38/// assert!((dew_point(15.0, 100.0) - 15.0).abs() < 1e-6);
39/// ```
40pub fn dew_point(celsius: f64, humidity_percent: f64) -> f64 {
41    let humidity = if humidity_percent <= 0.0 {
42        0.0001
43    } else {
44        humidity_percent
45    };
46    let gamma = log(humidity / 100.0) + MAGNUS_B * celsius / (MAGNUS_C + celsius);
47    MAGNUS_C * gamma / (MAGNUS_B - gamma)
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn matches_a_worked_example() {
56        // 20 C at 50% RH dews near 9.3 C with the Magnus coefficients b=17.62, c=243.12.
57        assert!((dew_point(20.0, 50.0) - 9.3).abs() < 0.2);
58    }
59
60    #[test]
61    fn saturated_air_dews_at_the_temperature() {
62        assert!((dew_point(15.0, 100.0) - 15.0).abs() < 1e-6);
63        assert!((dew_point(-3.0, 100.0) + 3.0).abs() < 1e-6);
64    }
65
66    #[test]
67    fn lower_humidity_means_a_lower_dew_point() {
68        let humid = dew_point(25.0, 80.0);
69        let dry = dew_point(25.0, 30.0);
70        assert!(dry < humid);
71    }
72
73    #[test]
74    fn a_frost_risk_shows_as_a_dew_point_below_zero() {
75        // Cold, fairly dry air dews below freezing.
76        assert!(dew_point(2.0, 60.0) < 0.0);
77    }
78}