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

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. 

14 

15"""Operators for humidity conversions.""" 

16 

17import iris.cube 

18 

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 

23 

24 

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. 

29 

30 Arguments 

31 --------- 

32 specific_humidity: iris.cube.Cube | iris.cube.CubeList 

33 Cubes of specific humidity to be converted to mixing ratio. 

34 

35 Returns 

36 ------- 

37 iris.cube.Cube | iris.cube.CubeList 

38 Converted mixing ratio. 

39 

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 

45 

46 .. math:: w = \frac{q}{1 - q} 

47 

48 with w the mixing ratio and q the specific humidity. 

49 

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). 

52 

53 

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 

68 

69 

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. 

74 

75 Arguments 

76 --------- 

77 mixing_ratio: iris.cube.Cube | iris.Cube.CubeList 

78 Cubes of mixing ratio to be converted to specific humidity. 

79 

80 Returns 

81 ------- 

82 iris.cube.Cube | iris.cube.CubeList 

83 Converted specific humidity. 

84 

85 Notes 

86 ----- 

87 Here, we invert the relationship from `humidity.mixing_ratio_from_specific_humidity` 

88 for the following relation 

89 

90 .. math:: q = \frac{w}{1 + w} 

91 

92 with q the specific humidity and w the mixing ratio. 

93 

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). 

96 

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 

111 

112 

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. 

118 

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. 

125 

126 Returns 

127 ------- 

128 iris.cube.Cube | iris.cube.CubeList 

129 Saturation mixing ratio in kg/kg. 

130 

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 

136 

137 .. math:: w = \epsilon \frac{e}{P - e} 

138 

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. 

144 

145 All cubes need to be on the same grid. 

146 

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 

162 

163 

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. 

169 

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. 

176 

177 Returns 

178 ------- 

179 iris.cube.Cube | iris.cube.CubeList 

180 Saturation specific humidity in kg/kg. 

181 

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 

187 

188 .. math:: q = \epsilon \frac{e}{P} 

189 

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. 

195 

196 All cubes need to be on the same grid. 

197 

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 

213 

214 

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. 

221 

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. 

230 

231 Returns 

232 ------- 

233 iris.cube.Cube | iris.cube.CubeList 

234 Calculated mixing ratio from relative humidity in kg/kg. 

235 

236 Notes 

237 ----- 

238 The mixing ratio can be calculated from temperature, pressure, and 

239 relative humidity using the following relation 

240 

241 .. math:: w = RH * w_s 

242 

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. 

246 

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. 

250 

251 All cubes need to be on the same grid. 

252 

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 

273 

274 

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. 

281 

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. 

290 

291 Returns 

292 ------- 

293 iris.cube.Cube | iris.cube.CubeList 

294 Calculated specific humidity from relative humidity in kg/kg. 

295 

296 Notes 

297 ----- 

298 The specific humidity can be calculated from temperature, pressure, and 

299 relative humidity using the following relation 

300 

301 .. math:: q = RH * q_{sat} 

302 

303 for q the specific humidity, :math:`q_{sat}` the saturation specific 

304 humidity, and RH the relative humidity. 

305 

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. 

309 

310 All cubes need to be on the same grid. 

311 

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 

332 

333 

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. 

340 

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. 

349 

350 Returns 

351 ------- 

352 iris.cube.Cube | iris.cube.CubeList 

353 Relative humidity calculated from mixing ratio. 

354 

355 Notes 

356 ----- 

357 The relative humidity can be calculated from the mixing ratio following 

358 

359 .. math:: RH = \frac{w}{w_s} 

360 

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`. 

364 

365 The RH varies predominatly between zero (completely dry) and one (saturated). 

366 Values larger than one are possible and imply supersaturation. 

367 

368 All cubes must be on the same grid. 

369 

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 

389 

390 

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. 

397 

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. 

406 

407 Returns 

408 ------- 

409 iris.cube.Cube | iris.cube.CubeList 

410 Relative humidity calculated from specific humidity. 

411 

412 Notes 

413 ----- 

414 The relative humidity can be calculated from the specific humidity following 

415 

416 .. math:: RH = \frac{q}{q_{sat}} 

417 

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`. 

421 

422 The RH varies predominatly between zero (completely dry) and one (saturated). 

423 Values larger than one are possible and imply supersaturation. 

424 

425 All cubes must be on the same grid. 

426 

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