Coverage for src / CSET / operators / humidity.py: 100%
89 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-28 11:16 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-28 11:16 +0000
1# © Crown copyright, Met Office (2022-2026) and CSET contributors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Operators for humidity conversions."""
17import iris.cube
19from CSET._common import iter_maybe
20from CSET.operators._atmospheric_constants import EPSILON
21from CSET.operators.misc import convert_units
22from CSET.operators.pressure import vapour_pressure
25def mixing_ratio_from_specific_humidity(
26 specific_humidity: iris.cube.Cube | iris.cube.CubeList,
27) -> iris.cube.Cube | iris.cube.CubeList:
28 r"""Convert specific humidity to mixing ratio.
30 Arguments
31 ---------
32 specific_humidity: iris.cube.Cube | iris.cube.CubeList
33 Cubes of specific humidity to be converted to mixing ratio.
35 Returns
36 -------
37 iris.cube.Cube | iris.cube.CubeList
38 Converted mixing ratio.
40 Notes
41 -----
42 Atmospheric water vapour can be described by multiple quantities. Here,
43 we convert the specific humidity to the mixing ratio using the following
44 relation
46 .. math:: w = \frac{q}{1 - q}
48 with w the mixing ratio and q the specific humidity.
50 Larger mixing ratios imply more moisture in the atmosphere. The mixing
51 ratio will have the same units as the specific humidity (kg/kg).
54 Examples
55 --------
56 >>> w = humidity.mixing_ratio_from_specific_humidity(specific_humidity)
57 """
58 w = iris.cube.CubeList([])
59 for q in iter_maybe(specific_humidity):
60 mr = q.copy()
61 mr = q / (1 - q)
62 mr.rename("mixing_ratio")
63 w.append(mr)
64 if len(w) == 1:
65 return w[0]
66 else:
67 return w
70def specific_humidity_from_mixing_ratio(
71 mixing_ratio: iris.cube.Cube | iris.cube.CubeList,
72) -> iris.cube.Cube | iris.cube.CubeList:
73 r"""Convert mixing ratio to specific humidity.
75 Arguments
76 ---------
77 mixing_ratio: iris.cube.Cube | iris.Cube.CubeList
78 Cubes of mixing ratio to be converted to specific humidity.
80 Returns
81 -------
82 iris.cube.Cube | iris.cube.CubeList
83 Converted specific humidity.
85 Notes
86 -----
87 Here, we invert the relationship from `humidity.mixing_ratio_from_specific_humidity`
88 for the following relation
90 .. math:: q = \frac{w}{1 + w}
92 with q the specific humidity and w the mixing ratio.
94 A larger specific humidity implies a more moist atmosphere. The specific
95 humidity will have the same units as the mixing ratio (kg/kg).
97 Examples
98 --------
99 >>> q = humidity.specific_humidity_from_mixing_ratio(mixing_ratio)
100 """
101 q = iris.cube.CubeList([])
102 for w in iter_maybe(mixing_ratio):
103 sh = w.copy()
104 sh = w / (1 + w)
105 sh.rename("specific_humidity")
106 q.append(sh)
107 if len(q) == 1:
108 return q[0]
109 else:
110 return q
113def saturation_mixing_ratio(
114 temperature: iris.cube.Cube | iris.cube.CubeList,
115 pressure: iris.cube.Cube | iris.cube.CubeList,
116) -> iris.cube.Cube | iris.cube.CubeList:
117 r"""Calculate saturation mixing ratio.
119 Arguments
120 ---------
121 temperature: iris.cube.Cube | iris.cube.CubeList
122 Cubes of temperature in Kelvin.
123 pressure: iris.cube.Cube | iris.cube.CubeList
124 Cubes of pressure.
126 Returns
127 -------
128 iris.cube.Cube | iris.cube.CubeList
129 Saturation mixing ratio in kg/kg.
131 Notes
132 -----
133 The saturation mixing ratio is required to help calculate the relative
134 humidity and other diagnostics with respect to the mixing ratio. It can
135 be calculated from
137 .. math:: w = \epsilon \frac{e}{P - e}
139 for w the mixing ratio, :math:`\epsilon` the ratio between the mixing ratio
140 of dry and moist air equating to 0.622, P the pressure and e the vapour
141 pressure. To ensure that the saturation mixing ratio (:math:`w_s`) is
142 calculated the vapour pressure calculated should be with the (dry-bulb)
143 temperature to ensure it is the saturated vapour pressure.
145 All cubes need to be on the same grid.
147 Examples
148 --------
149 >>> w_s = humidity.saturation_mixing_ratio(temperature, pressure)
150 """
151 w = iris.cube.CubeList([])
152 for T, P in zip(iter_maybe(temperature), iter_maybe(pressure), strict=True):
153 P = convert_units(P, "hPa")
154 mr = (EPSILON * vapour_pressure(T)) / (P - vapour_pressure(T))
155 mr.units = "kg/kg"
156 mr.rename("saturation_mixing_ratio")
157 w.append(mr)
158 if len(w) == 1:
159 return w[0]
160 else:
161 return w
164def saturation_specific_humidity(
165 temperature: iris.cube.Cube | iris.cube.CubeList,
166 pressure: iris.cube.Cube | iris.cube.CubeList,
167) -> iris.cube.Cube | iris.cube.CubeList:
168 r"""Calculate saturation specific humidity.
170 Arguments
171 ---------
172 temperature: iris.cube.Cube | iris.cube.CubeList
173 Cubes of temperature in Kelvin.
174 pressure: iris.cube.Cube | iris.cube.CubeList
175 Cubes of pressure.
177 Returns
178 -------
179 iris.cube.Cube | iris.cube.CubeList
180 Saturation specific humidity in kg/kg.
182 Notes
183 -----
184 The saturation specific humidity is required to help calculate the relative
185 humidity and other diagnostics with respect to the mixing ratio. It can
186 be calculated from
188 .. math:: q = \epsilon \frac{e}{P}
190 for q the specific humidity, :math:`\epsilon` the ratio between the mixing ratio
191 of dry and moist air equating to 0.622, P the pressure and e the vapour
192 pressure. To ensure that the saturation specific humidity (:math:`q_{sat}`) is
193 calculated the vapour pressure calculated should be with the (dry-bulb)
194 temperature to ensure it is the saturated vapour pressure.
196 All cubes need to be on the same grid.
198 Examples
199 --------
200 >>> q_sat = humidity.saturation_specific_humidity(temperature, pressure)
201 """
202 q = iris.cube.CubeList([])
203 for T, P in zip(iter_maybe(temperature), iter_maybe(pressure), strict=True):
204 P = convert_units(P, "hPa")
205 sh = (EPSILON * vapour_pressure(T)) / P
206 sh.units = "kg/kg"
207 sh.rename("saturation_specific_humidity")
208 q.append(sh)
209 if len(q) == 1:
210 return q[0]
211 else:
212 return q
215def mixing_ratio_from_relative_humidity(
216 temperature: iris.cube.Cube | iris.cube.CubeList,
217 pressure: iris.cube.Cube | iris.cube.CubeList,
218 relative_humidity: iris.cube.Cube | iris.cube.CubeList,
219) -> iris.cube.Cube | iris.cube.CubeList:
220 r"""Calculate the mixing ratio from RH.
222 Arguments
223 ---------
224 temperature: iris.cube.Cube | iris.cube.CubeList
225 Cubes of temperature in Kelvin.
226 pressure: iris.cube.Cube | iris.cube.CubeList
227 Cubes of pressure.
228 relative_humidity: iris.cube.Cube | iris.cube.CubeList
229 Cubes of relative humidity.
231 Returns
232 -------
233 iris.cube.Cube | iris.cube.CubeList
234 Calculated mixing ratio from relative humidity in kg/kg.
236 Notes
237 -----
238 The mixing ratio can be calculated from temperature, pressure, and
239 relative humidity using the following relation
241 .. math:: w = RH * w_s
243 for w the mixing ratio, :math:`w_s` the saturation mixing ratio, and
244 RH the relative humidity. RH is converted to dimensionless fraction rather
245 than percentage.
247 The operator uses `humidity.saturation_mixing_ratio` to calculate the
248 saturation mixing ratio from the temperature and pressure. The relative
249 humidity is converted into a decimal before the multiplication occurs.
251 All cubes need to be on the same grid.
253 Examples
254 --------
255 >>> w = humidity.mixing_ratio_from_relative_humidity(T, P, RH)
256 """
257 w = iris.cube.CubeList([])
258 for T, P, RH in zip(
259 iter_maybe(temperature),
260 iter_maybe(pressure),
261 iter_maybe(relative_humidity),
262 strict=True,
263 ):
264 RH = convert_units(RH, "1")
265 mr = saturation_mixing_ratio(T, P) * RH
266 mr.rename("mixing_ratio")
267 mr.units = "kg/kg"
268 w.append(mr)
269 if len(w) == 1:
270 return w[0]
271 else:
272 return w
275def specific_humidity_from_relative_humidity(
276 temperature: iris.cube.Cube | iris.cube.CubeList,
277 pressure: iris.cube.Cube | iris.cube.CubeList,
278 relative_humidity: iris.cube.Cube | iris.cube.CubeList,
279) -> iris.cube.Cube | iris.cube.CubeList:
280 r"""Calculate the specific humidity from relative humidity.
282 Arguments
283 ---------
284 temperature: iris.cube.Cube | iris.cube.CubeList
285 Cubes of temperature in Kelvin.
286 pressure: iris.cube.Cube | iris.cube.CubeList
287 Cubes of pressure.
288 relative_humidity: iris.cube.Cube | iris.cube.CubeList
289 Cubes of relative humidity.
291 Returns
292 -------
293 iris.cube.Cube | iris.cube.CubeList
294 Calculated specific humidity from relative humidity in kg/kg.
296 Notes
297 -----
298 The specific humidity can be calculated from temperature, pressure, and
299 relative humidity using the following relation
301 .. math:: q = RH * q_{sat}
303 for q the specific humidity, :math:`q_{sat}` the saturation specific
304 humidity, and RH the relative humidity.
306 The operator uses `humidity.saturation_specific_humidity` to calculate the
307 saturation specific humidity from the temperature and pressure. The relative
308 humidity is converted into a decimal before the multiplication occurs.
310 All cubes need to be on the same grid.
312 Examples
313 --------
314 >>> q = humidity.specific_humidity_from_relative_humidity(T, P, RH)
315 """
316 q = iris.cube.CubeList([])
317 for T, P, RH in zip(
318 iter_maybe(temperature),
319 iter_maybe(pressure),
320 iter_maybe(relative_humidity),
321 strict=True,
322 ):
323 RH = convert_units(RH, "1")
324 sh = saturation_specific_humidity(T, P) * RH
325 sh.rename("specific_humidity")
326 sh.units = "kg/kg"
327 q.append(sh)
328 if len(q) == 1:
329 return q[0]
330 else:
331 return q
334def relative_humidity_from_mixing_ratio(
335 mixing_ratio: iris.cube.Cube | iris.cube.CubeList,
336 temperature: iris.cube.Cube | iris.cube.CubeList,
337 pressure: iris.cube.Cube | iris.cube.CubeList,
338) -> iris.cube.Cube | iris.cube.CubeList:
339 r"""Convert mixing ratio to relative humidity.
341 Arguments
342 ---------
343 mixing_ratio: iris.cube.Cube | iris.cube.CubeList
344 Cubes of mixing ratio.
345 temperature: iris.cube.Cube | iris.cube.CuebList
346 Cubes of temperature in Kelvin.
347 pressure: iris.cube.Cube | iris.cube.CubeList
348 Cubes of pressure.
350 Returns
351 -------
352 iris.cube.Cube | iris.cube.CubeList
353 Relative humidity calculated from mixing ratio.
355 Notes
356 -----
357 The relative humidity can be calculated from the mixing ratio following
359 .. math:: RH = \frac{w}{w_s}
361 for RH the relative humidity, w the mixing ratio, and :math:`w_s` the
362 saturation mixing ratio. The saturation mixing ratio is calculated using
363 `humidity.saturation_mixing_ratio`.
365 The RH varies predominatly between zero (completely dry) and one (saturated).
366 Values larger than one are possible and imply supersaturation.
368 All cubes must be on the same grid.
370 Examples
371 --------
372 >>> RH = humidity.relative_humidity_from_mixing_ratio(w, T, P)
373 """
374 RH = iris.cube.CubeList([])
375 for W, T, P in zip(
376 iter_maybe(mixing_ratio),
377 iter_maybe(temperature),
378 iter_maybe(pressure),
379 strict=True,
380 ):
381 rel_h = W / saturation_mixing_ratio(T, P)
382 rel_h.rename("relative_humidity")
383 rel_h = convert_units(rel_h, "%")
384 RH.append(rel_h)
385 if len(RH) == 1:
386 return RH[0]
387 else:
388 return RH
391def relative_humidity_from_specific_humidity(
392 specific_humidity: iris.cube.Cube | iris.cube.CubeList,
393 temperature: iris.cube.Cube | iris.cube.CubeList,
394 pressure: iris.cube.Cube | iris.cube.CubeList,
395) -> iris.cube.Cube | iris.cube.CubeList:
396 r"""Convert specific humidity to relative humidity.
398 Arguments
399 ---------
400 specific_humidity: iris.cube.Cube | iris.cube.CubeList
401 Cubes of specific humidity.
402 temperature: iris.cube.Cube | iris.cube.CuebList
403 Cubes of temperature in Kelvin.
404 pressure: iris.cube.Cube | iris.cube.CubeList
405 Cubes of pressure.
407 Returns
408 -------
409 iris.cube.Cube | iris.cube.CubeList
410 Relative humidity calculated from specific humidity.
412 Notes
413 -----
414 The relative humidity can be calculated from the specific humidity following
416 .. math:: RH = \frac{q}{q_{sat}}
418 for RH the relative humidity, q the specific humidity, and :math:`q_{sat}` the
419 saturation specific humidity. The saturation specific humidity is calculated using
420 `humidity.saturation_specific_humidity`.
422 The RH varies predominatly between zero (completely dry) and one (saturated).
423 Values larger than one are possible and imply supersaturation.
425 All cubes must be on the same grid.
427 Examples
428 --------
429 >>> RH = humidity.relative_humidity_from_specific_humidity(q, T, P)
430 """
431 RH = iris.cube.CubeList([])
432 for Q, T, P in zip(
433 iter_maybe(specific_humidity),
434 iter_maybe(temperature),
435 iter_maybe(pressure),
436 strict=True,
437 ):
438 rel_h = Q / saturation_specific_humidity(T, P)
439 rel_h.rename("relative_humidity")
440 rel_h = convert_units(rel_h, "%")
441 RH.append(rel_h)
442 if len(RH) == 1:
443 return RH[0]
444 else:
445 return RH