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}