floccus/
equivalent_potential_temperature.rs

1//!Functions to calculate equivalent potential temperature of air in K.
2use crate::constants::{C_L, R_V};
3use crate::Float;
4use crate::{
5    constants::{C_P, EPSILON, L_V, R_D},
6    errors::InputError,
7    mixing_ratio, potential_temperature, relative_humidity, vapour_pressure,
8};
9
10#[cfg(feature = "debug")]
11use floccus_proc::logerr;
12
13///Most accuarte formula for computing equivalent potential temperature of unsaturated air from
14///temperature, pressure and vapour pressure.
15///
16///Implementation of this formula assumes no liquid or solid water in the air parcel.
17///
18///Provided in Emmanuel, Kerry (1994). Atmospheric Convection. Oxford University Press.
19///
20///# Errors
21///
22///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\
23///Valid `temperature` range: 253K - 324K\
24///Valid `pressure` range: 100Pa - 150000Pa\
25///Valid `vapour_pressure` range: 0Pa - 10000Pa
26#[cfg_attr(feature = "debug", logerr)]
27pub fn general1(
28    temperature: Float,
29    pressure: Float,
30    vapour_pressure: Float,
31) -> Result<Float, InputError> {
32    if !(253.0..=324.0).contains(&temperature) {
33        return Err(InputError::OutOfRange(String::from("temperature")));
34    }
35
36    if !(20000.0..=150_000.0).contains(&pressure) {
37        return Err(InputError::OutOfRange(String::from("pressure")));
38    }
39
40    if !(0.0..=10_000.0).contains(&vapour_pressure) {
41        return Err(InputError::OutOfRange(String::from("vapour_pressure")));
42    }
43
44    let p0 = 100_000.0;
45
46    let mixing_ratio = mixing_ratio::general1(pressure, vapour_pressure)?;
47    let saturation_vapour_pressure = vapour_pressure::buck1(temperature, pressure)?;
48
49    let relative_humidity =
50        relative_humidity::general2(vapour_pressure, saturation_vapour_pressure)?;
51
52    let result = temperature
53        * (p0 / pressure).powf(R_D / (C_P + mixing_ratio * C_L))
54        * relative_humidity.powf((-mixing_ratio * R_V) / (C_P + mixing_ratio * C_L))
55        * ((L_V * mixing_ratio) / (temperature * (C_P + mixing_ratio * C_L))).exp();
56
57    Ok(result)
58}
59
60///Formula for computing equivalent potential temperature of unsaturated air from
61///temperature, pressure and vapour pressure.
62///
63///Derived by G. H. Bryan (2008) [(doi:10.1175/2008MWR2593.1)](https://doi.org/10.1175/2008MWR2593.1)
64///
65///# Errors
66///
67///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\
68///Valid `temperature` range: 253K - 324K\
69///Valid `pressure` range: 100Pa - 150000Pa\
70///Valid `vapour_pressure` range: 0Pa - 10000Pa
71#[cfg_attr(feature = "debug", logerr)]
72pub fn bryan1(
73    temperature: Float,
74    pressure: Float,
75    vapour_pressure: Float,
76) -> Result<Float, InputError> {
77    if !(253.0..=324.0).contains(&temperature) {
78        return Err(InputError::OutOfRange(String::from("temperature")));
79    }
80
81    if !(20000.0..=150_000.0).contains(&pressure) {
82        return Err(InputError::OutOfRange(String::from("pressure")));
83    }
84
85    if !(0.0..=10_000.0).contains(&vapour_pressure) {
86        return Err(InputError::OutOfRange(String::from("vapour_pressure")));
87    }
88
89    let kappa = R_D / C_P;
90
91    let potential_temperature =
92        potential_temperature::davies_jones1(temperature, pressure, vapour_pressure)?;
93
94    let saturation_vapour_pressure = vapour_pressure::buck3(temperature, pressure)?;
95    let relative_humidity =
96        relative_humidity::general2(vapour_pressure, saturation_vapour_pressure)?;
97
98    let mixing_ratio = mixing_ratio::general1(pressure, vapour_pressure)?;
99
100    let result = potential_temperature
101        * relative_humidity.powf((-kappa) * (mixing_ratio / EPSILON))
102        * ((L_V * mixing_ratio) / (C_P * temperature)).exp();
103
104    Ok(result)
105}
106
107///Approximate formula for computing equivalent potential temperature of unsaturated air from
108///temperature, pressure and dewpoint.
109///
110///Derived by D. Bolton (1980)
111///[(doi:10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2)](https://doi.org/10.1175/1520-0493(1980)108%3C1046:TCOEPT%3E2.0.CO;2)
112///
113///# Errors
114///
115///Returns [`InputError::OutOfRange`] when one of inputs is out of range.\
116///Valid `pressure` range: 100Pa - 150000Pa\
117///Valid `temperature` range: 253K - 324K\
118///Valid `dewpoint` range: 253K - 324K
119#[cfg_attr(feature = "debug", logerr)]
120pub fn bolton1(pressure: Float, temperature: Float, dewpoint: Float) -> Result<Float, InputError> {
121    if !(20000.0..=150_000.0).contains(&pressure) {
122        return Err(InputError::OutOfRange(String::from("pressure")));
123    }
124
125    if !(253.0..=324.0).contains(&temperature) {
126        return Err(InputError::OutOfRange(String::from("temperature")));
127    }
128
129    if !(253.0..=324.0).contains(&dewpoint) {
130        return Err(InputError::OutOfRange(String::from("dewpoint")));
131    }
132
133    let kappa = R_D / C_P;
134
135    let vapour_pressure = vapour_pressure::buck3(dewpoint, pressure)?;
136    let mixing_ratio = mixing_ratio::general1(pressure, vapour_pressure)?;
137
138    let lcl_temp =
139        (1.0 / ((1.0 / (dewpoint - 56.0)) + ((temperature / dewpoint).ln() / 800.0))) + 56.0;
140
141    let theta_dl = temperature
142        * (100000.0 / (pressure - vapour_pressure)).powf(kappa)
143        * (temperature / lcl_temp).powf(0.28 * mixing_ratio);
144
145    let result = theta_dl
146        * (((3036.0 / lcl_temp) - 1.78) * mixing_ratio * (1.0 + 0.448 * mixing_ratio)).exp();
147
148    Ok(result)
149}
150
151#[cfg(test)]
152mod tests {
153    use crate::{
154        equivalent_potential_temperature,
155        tests_framework::{self, Argument},
156    };
157
158    #[test]
159    fn general1() {
160        assert!(tests_framework::test_with_3args(
161            &equivalent_potential_temperature::general1,
162            Argument {
163                name: "temperature",
164                def_val: 300.0,
165                range: [253.0, 324.0]
166            },
167            Argument {
168                name: "pressure",
169                def_val: 101325.0,
170                range: [20000.0, 150_000.0]
171            },
172            Argument {
173                name: "vapour_pressure",
174                def_val: 991.189131,
175                range: [0.0, 10_000.0]
176            },
177            315.23724970376776
178        ));
179    }
180
181    #[test]
182    fn bryan1() {
183        assert!(tests_framework::test_with_3args(
184            &equivalent_potential_temperature::bryan1,
185            Argument {
186                name: "temperature",
187                def_val: 300.0,
188                range: [253.0, 324.0]
189            },
190            Argument {
191                name: "pressure",
192                def_val: 101325.0,
193                range: [20000.0, 150_000.0]
194            },
195            Argument {
196                name: "vapour_pressure",
197                def_val: 991.189131,
198                range: [0.0, 10_000.0]
199            },
200            316.52762026634014
201        ));
202    }
203
204    #[test]
205    fn bolton1() {
206        assert!(tests_framework::test_with_3args(
207            &equivalent_potential_temperature::bolton1,
208            Argument {
209                name: "pressure",
210                def_val: 101325.0,
211                range: [20000.0, 150_000.0]
212            },
213            Argument {
214                name: "temperature",
215                def_val: 300.0,
216                range: [253.0, 324.0]
217            },
218            Argument {
219                name: "dewpoint",
220                def_val: 280.0,
221                range: [253.0, 324.0]
222            },
223            317.3855211897774
224        ));
225    }
226}