Coverage for src/CSET/operators/humidity.py: 100%
139 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-09 07:49 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-09 07:49 +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
18import numpy as np
20from CSET._common import iter_maybe
21from CSET.operators._atmospheric_constants import EPSILON
22from CSET.operators._utils import get_cube_coordindex
23from CSET.operators.misc import convert_units
24from CSET.operators.pressure import vapour_pressure
27def mixing_ratio_from_specific_humidity(
28 specific_humidity: iris.cube.Cube | iris.cube.CubeList,
29) -> iris.cube.Cube | iris.cube.CubeList:
30 r"""Convert specific humidity to mixing ratio.
32 Arguments
33 ---------
34 specific_humidity: iris.cube.Cube | iris.cube.CubeList
35 Cubes of specific humidity to be converted to mixing ratio.
37 Returns
38 -------
39 iris.cube.Cube | iris.cube.CubeList
40 Converted mixing ratio.
42 Notes
43 -----
44 Atmospheric water vapour can be described by multiple quantities. Here,
45 we convert the specific humidity to the mixing ratio using the following
46 relation
48 .. math:: w = \frac{q}{1 - q}
50 with w the mixing ratio and q the specific humidity.
52 Larger mixing ratios imply more moisture in the atmosphere. The mixing
53 ratio will have the same units as the specific humidity (kg/kg).
56 Examples
57 --------
58 >>> w = humidity.mixing_ratio_from_specific_humidity(specific_humidity)
59 """
60 w = iris.cube.CubeList([])
61 for q in iter_maybe(specific_humidity):
62 mr = q.copy()
63 mr = q / (1 - q)
64 mr.rename("mixing_ratio")
65 w.append(mr)
66 if len(w) == 1:
67 return w[0]
68 else:
69 return w
72def specific_humidity_from_mixing_ratio(
73 mixing_ratio: iris.cube.Cube | iris.cube.CubeList,
74) -> iris.cube.Cube | iris.cube.CubeList:
75 r"""Convert mixing ratio to specific humidity.
77 Arguments
78 ---------
79 mixing_ratio: iris.cube.Cube | iris.Cube.CubeList
80 Cubes of mixing ratio to be converted to specific humidity.
82 Returns
83 -------
84 iris.cube.Cube | iris.cube.CubeList
85 Converted specific humidity.
87 Notes
88 -----
89 Here, we invert the relationship from `humidity.mixing_ratio_from_specific_humidity`
90 for the following relation
92 .. math:: q = \frac{w}{1 + w}
94 with q the specific humidity and w the mixing ratio.
96 A larger specific humidity implies a more moist atmosphere. The specific
97 humidity will have the same units as the mixing ratio (kg/kg).
99 Examples
100 --------
101 >>> q = humidity.specific_humidity_from_mixing_ratio(mixing_ratio)
102 """
103 q = iris.cube.CubeList([])
104 for w in iter_maybe(mixing_ratio):
105 sh = w.copy()
106 sh = w / (1 + w)
107 sh.rename("specific_humidity")
108 q.append(sh)
109 if len(q) == 1:
110 return q[0]
111 else:
112 return q
115def saturation_mixing_ratio(
116 temperature: iris.cube.Cube | iris.cube.CubeList,
117 pressure: iris.cube.Cube | iris.cube.CubeList,
118) -> iris.cube.Cube | iris.cube.CubeList:
119 r"""Calculate saturation mixing ratio.
121 Arguments
122 ---------
123 temperature: iris.cube.Cube | iris.cube.CubeList
124 Cubes of temperature in Kelvin.
125 pressure: iris.cube.Cube | iris.cube.CubeList
126 Cubes of pressure.
128 Returns
129 -------
130 iris.cube.Cube | iris.cube.CubeList
131 Saturation mixing ratio in kg/kg.
133 Notes
134 -----
135 The saturation mixing ratio is required to help calculate the relative
136 humidity and other diagnostics with respect to the mixing ratio. It can
137 be calculated from
139 .. math:: w = \epsilon \frac{e}{P - e}
141 for w the mixing ratio, :math:`\epsilon` the ratio between the mixing ratio
142 of dry and moist air equating to 0.62197, P the pressure and e the vapour
143 pressure. To ensure that the saturation mixing ratio (:math:`w_s`) is
144 calculated the vapour pressure calculated should be with the (dry-bulb)
145 temperature to ensure it is the saturated vapour pressure.
147 All cubes need to be on the same grid.
149 Examples
150 --------
151 >>> w_s = humidity.saturation_mixing_ratio(temperature, pressure)
152 """
153 w = iris.cube.CubeList([])
154 for T, P in zip(iter_maybe(temperature), iter_maybe(pressure), strict=True):
155 P = convert_units(P, "hPa")
156 mr = (EPSILON * vapour_pressure(T)) / (P - vapour_pressure(T))
157 mr.units = "kg/kg"
158 mr.rename("saturation_mixing_ratio")
159 w.append(mr)
160 if len(w) == 1:
161 return w[0]
162 else:
163 return w
166def saturation_specific_humidity(
167 temperature: iris.cube.Cube | iris.cube.CubeList,
168 pressure: iris.cube.Cube | iris.cube.CubeList,
169) -> iris.cube.Cube | iris.cube.CubeList:
170 r"""Calculate saturation specific humidity.
172 Arguments
173 ---------
174 temperature: iris.cube.Cube | iris.cube.CubeList
175 Cubes of temperature in Kelvin.
176 pressure: iris.cube.Cube | iris.cube.CubeList
177 Cubes of pressure.
179 Returns
180 -------
181 iris.cube.Cube | iris.cube.CubeList
182 Saturation specific humidity in kg/kg.
184 Notes
185 -----
186 The saturation specific humidity is required to help calculate the relative
187 humidity and other diagnostics with respect to the mixing ratio. It can
188 be calculated from
190 .. math:: q = \epsilon \frac{e}{P}
192 for q the specific humidity, :math:`\epsilon` the ratio between the mixing ratio
193 of dry and moist air equating to 0.62197, P the pressure and e the vapour
194 pressure. To ensure that the saturation specific humidity (:math:`q_{sat}`) is
195 calculated the vapour pressure calculated should be with the (dry-bulb)
196 temperature to ensure it is the saturated vapour pressure.
198 All cubes need to be on the same grid.
200 Examples
201 --------
202 >>> q_sat = humidity.saturation_specific_humidity(temperature, pressure)
203 """
204 q = iris.cube.CubeList([])
205 for T, P in zip(iter_maybe(temperature), iter_maybe(pressure), strict=True):
206 P = convert_units(P, "hPa")
207 sh = (EPSILON * vapour_pressure(T)) / P
208 sh.units = "kg/kg"
209 sh.rename("saturation_specific_humidity")
210 q.append(sh)
211 if len(q) == 1:
212 return q[0]
213 else:
214 return q
217def mixing_ratio_from_relative_humidity(
218 temperature: iris.cube.Cube | iris.cube.CubeList,
219 pressure: iris.cube.Cube | iris.cube.CubeList,
220 relative_humidity: iris.cube.Cube | iris.cube.CubeList,
221) -> iris.cube.Cube | iris.cube.CubeList:
222 r"""Calculate the mixing ratio from RH.
224 Arguments
225 ---------
226 temperature: iris.cube.Cube | iris.cube.CubeList
227 Cubes of temperature in Kelvin.
228 pressure: iris.cube.Cube | iris.cube.CubeList
229 Cubes of pressure.
230 relative_humidity: iris.cube.Cube | iris.cube.CubeList
231 Cubes of relative humidity.
233 Returns
234 -------
235 iris.cube.Cube | iris.cube.CubeList
236 Calculated mixing ratio from relative humidity in kg/kg.
238 Notes
239 -----
240 The mixing ratio can be calculated from temperature, pressure, and
241 relative humidity using the following relation
243 .. math:: w = RH * w_s
245 for w the mixing ratio, :math:`w_s` the saturation mixing ratio, and
246 RH the relative humidity. RH is converted to dimensionless fraction rather
247 than percentage.
249 The operator uses `humidity.saturation_mixing_ratio` to calculate the
250 saturation mixing ratio from the temperature and pressure. The relative
251 humidity is converted into a decimal before the multiplication occurs.
253 All cubes need to be on the same grid.
255 Examples
256 --------
257 >>> w = humidity.mixing_ratio_from_relative_humidity(T, P, RH)
258 """
259 w = iris.cube.CubeList([])
260 for T, P, RH in zip(
261 iter_maybe(temperature),
262 iter_maybe(pressure),
263 iter_maybe(relative_humidity),
264 strict=True,
265 ):
266 RH = convert_units(RH, "1")
267 mr = saturation_mixing_ratio(T, P) * RH
268 mr.rename("mixing_ratio")
269 mr.units = "kg/kg"
270 w.append(mr)
271 if len(w) == 1:
272 return w[0]
273 else:
274 return w
277def specific_humidity_from_relative_humidity(
278 temperature: iris.cube.Cube | iris.cube.CubeList,
279 pressure: iris.cube.Cube | iris.cube.CubeList,
280 relative_humidity: iris.cube.Cube | iris.cube.CubeList,
281) -> iris.cube.Cube | iris.cube.CubeList:
282 r"""Calculate the specific humidity from relative humidity.
284 Arguments
285 ---------
286 temperature: iris.cube.Cube | iris.cube.CubeList
287 Cubes of temperature in Kelvin.
288 pressure: iris.cube.Cube | iris.cube.CubeList
289 Cubes of pressure.
290 relative_humidity: iris.cube.Cube | iris.cube.CubeList
291 Cubes of relative humidity.
293 Returns
294 -------
295 iris.cube.Cube | iris.cube.CubeList
296 Calculated specific humidity from relative humidity in kg/kg.
298 Notes
299 -----
300 The specific humidity can be calculated from temperature, pressure, and
301 relative humidity using the following relation
303 .. math:: q = RH * q_{sat}
305 for q the specific humidity, :math:`q_{sat}` the saturation specific
306 humidity, and RH the relative humidity.
308 The operator uses `humidity.saturation_specific_humidity` to calculate the
309 saturation specific humidity from the temperature and pressure. The relative
310 humidity is converted into a decimal before the multiplication occurs.
312 All cubes need to be on the same grid.
314 Examples
315 --------
316 >>> q = humidity.specific_humidity_from_relative_humidity(T, P, RH)
317 """
318 q = iris.cube.CubeList([])
319 for T, P, RH in zip(
320 iter_maybe(temperature),
321 iter_maybe(pressure),
322 iter_maybe(relative_humidity),
323 strict=True,
324 ):
325 RH = convert_units(RH, "1")
326 sh = saturation_specific_humidity(T, P) * RH
327 sh.rename("specific_humidity")
328 sh.units = "kg/kg"
329 q.append(sh)
330 if len(q) == 1:
331 return q[0]
332 else:
333 return q
336def relative_humidity_from_mixing_ratio(
337 mixing_ratio: iris.cube.Cube | iris.cube.CubeList,
338 temperature: iris.cube.Cube | iris.cube.CubeList,
339 pressure: iris.cube.Cube | iris.cube.CubeList,
340) -> iris.cube.Cube | iris.cube.CubeList:
341 r"""Convert mixing ratio to relative humidity.
343 Arguments
344 ---------
345 mixing_ratio: iris.cube.Cube | iris.cube.CubeList
346 Cubes of mixing ratio.
347 temperature: iris.cube.Cube | iris.cube.CuebList
348 Cubes of temperature in Kelvin.
349 pressure: iris.cube.Cube | iris.cube.CubeList
350 Cubes of pressure.
352 Returns
353 -------
354 iris.cube.Cube | iris.cube.CubeList
355 Relative humidity calculated from mixing ratio.
357 Notes
358 -----
359 The relative humidity can be calculated from the mixing ratio following
361 .. math:: RH = \frac{w}{w_s}
363 for RH the relative humidity, w the mixing ratio, and :math:`w_s` the
364 saturation mixing ratio. The saturation mixing ratio is calculated using
365 `humidity.saturation_mixing_ratio`.
367 The RH varies predominatly between zero (completely dry) and one (saturated).
368 Values larger than one are possible and imply supersaturation.
370 All cubes must be on the same grid.
372 Examples
373 --------
374 >>> RH = humidity.relative_humidity_from_mixing_ratio(w, T, P)
375 """
376 RH = iris.cube.CubeList([])
377 for W, T, P in zip(
378 iter_maybe(mixing_ratio),
379 iter_maybe(temperature),
380 iter_maybe(pressure),
381 strict=True,
382 ):
383 rel_h = W / saturation_mixing_ratio(T, P)
384 rel_h.rename("relative_humidity")
385 rel_h = convert_units(rel_h, "%")
386 RH.append(rel_h)
387 if len(RH) == 1:
388 return RH[0]
389 else:
390 return RH
393def relative_humidity_from_specific_humidity(
394 specific_humidity: iris.cube.Cube | iris.cube.CubeList,
395 temperature: iris.cube.Cube | iris.cube.CubeList,
396 pressure: iris.cube.Cube | iris.cube.CubeList,
397) -> iris.cube.Cube | iris.cube.CubeList:
398 r"""Convert specific humidity to relative humidity.
400 Arguments
401 ---------
402 specific_humidity: iris.cube.Cube | iris.cube.CubeList
403 Cubes of specific humidity.
404 temperature: iris.cube.Cube | iris.cube.CuebList
405 Cubes of temperature in Kelvin.
406 pressure: iris.cube.Cube | iris.cube.CubeList
407 Cubes of pressure.
409 Returns
410 -------
411 iris.cube.Cube | iris.cube.CubeList
412 Relative humidity calculated from specific humidity.
414 Notes
415 -----
416 The relative humidity can be calculated from the specific humidity following
418 .. math:: RH = \frac{q}{q_{sat}}
420 for RH the relative humidity, q the specific humidity, and :math:`q_{sat}` the
421 saturation specific humidity. The saturation specific humidity is calculated using
422 `humidity.saturation_specific_humidity`.
424 The RH varies predominantly between zero (completely dry) and one (saturated).
425 Values larger than one are possible and imply supersaturation.
427 All cubes must be on the same grid.
429 Examples
430 --------
431 >>> RH = humidity.relative_humidity_from_specific_humidity(q, T, P)
432 """
433 RH = iris.cube.CubeList([])
434 for Q, T, P in zip(
435 iter_maybe(specific_humidity),
436 iter_maybe(temperature),
437 iter_maybe(pressure),
438 strict=True,
439 ):
440 rel_h = Q / saturation_specific_humidity(T, P)
441 rel_h.rename("relative_humidity")
442 rel_h = convert_units(rel_h, "%")
443 RH.append(rel_h)
444 if len(RH) == 1:
445 return RH[0]
446 else:
447 return RH
450def precipitable_water(
451 mixing_ratio: iris.cube.Cube | iris.cube.CubeList,
452) -> iris.cube.Cube | iris.cube.CubeList:
453 r"""Calculate the precipitable water.
455 Arguments
456 ---------
457 mixing_ratio: iris.cube.Cube | iris.cube.CubeList
458 A cube or cubelist of the mixing ratio. It can be
459 calculated within a recipe or a direct model output.
461 Returns
462 -------
463 iris.cube.Cube | iris.cube.CubeList
464 A cube or cubelist of the precipitable water.
466 Notes
467 -----
468 The precipitable water is the total depth of liquid water produced by
469 condensing all of the moisture in a column of the atmopshere.
471 It can be calculated as
473 .. math:: pw = frac{1}{\rho_w} \int w dz
475 for pw the precipitable water, ..math::\rho_{w} the density of water,
476 w the mixing ratio, and z the height. It is integrated from the surface
477 to the top of the atmosphere.
479 Generally, the precipitable water is widely applicable across the globe.
480 It is likely that larger precipitation totals are associated with greater
481 precipitable water. However, this is not strictly the case and you can get
482 lower precipitable water values with large precipitaiton amounts
483 (e.g. [Daviesetal24]_). Therefore, caution is needed with its interpretation.
484 A diagnostic such as saturation fraction maybe more beneficial (e.g. [Daviesetal26]_).
486 Examples
487 --------
488 >>> pwat = humidity.precipitable_water(mixing_ratio)
490 References
491 ----------
492 .. [Daviesetal24] Davies, P.A., Fowler, H.J, Villalobos-Herrera, R.,
493 Slingo, J., Flack, D.L.A., and Taszarek, M (2024)
494 "A New Conceptual Model for Understanding and Predicting Life-Threatening
495 Rainfall Extremes." Weather and Climate Extremes, vol. 45, 100696,
496 doi: 10.1016/j.wace.2024.100696
497 .. [Daviesetal26] Davies, P. A., Flack, D. L. A., Pirret, J., Fowler, H. J.
498 (2026) "Application of the Davies Four-Stage Conceptual Model for
499 Life-Threatening Rainfall Extremes on the April 2024 United Arab Emirates
500 and Oman Floods." Weather and Climate Extremes, vol. 51, 100846.
501 doi:10.1016/j.wace.2025.100846
502 """
503 precipitable_water = iris.cube.CubeList([])
504 for w in iter_maybe(mixing_ratio):
505 # Integrate the data in the vertical using np.trapezoid
506 # (following trapezoid rule).
507 pw = np.trapezoid(
508 w.data,
509 x=w.coord("level_height").points[:],
510 axis=get_cube_coordindex(w, "level_height"),
511 )
512 # Determine array information of input cube to get
513 # correct cube to copy across to.
514 if len(w.coord("realization").points) != 1 and len(w.coord("time").points) != 1:
515 pwat = w[:, :, 0, :, :].copy()
516 elif (
517 len(w.coord("realization").points) != 1 and len(w.coord("time").points) == 1
518 ):
519 pwat = w[:, 0, :, :].copy()
520 elif (
521 len(w.coord("time").points) != 1 and len(w.coord("realization").points) == 1
522 ):
523 pwat = w[:, 0, :, :].copy()
524 else:
525 pwat = w[0, :, :].copy()
526 # Create the data array, rename, and correct units.
527 pwat.data = pw
528 pwat.rename("precipitable_water")
529 # Setting units to mm to account for normalization by density of water.
530 pwat.units = "mm"
531 precipitable_water.append(pwat)
532 # Output the data.
533 if len(precipitable_water) == 1:
534 return precipitable_water[0]
535 else:
536 return precipitable_water
539def saturation_precipitable_water(
540 mixing_ratio: iris.cube.Cube | iris.cube.CubeList,
541 relative_humidity: iris.cube.Cube | iris.cube.CubeList,
542) -> iris.cube.Cube | iris.cube.CubeList:
543 r"""Calculate saturation precipitable water.
545 Arguments
546 ---------
547 mixing_ratio: iris.cube.Cube | iris.cube.CubeList
548 A cube or cubelist of the mixing ratio. It can be
549 calculated within a recipe or a direct model output.
550 relative_humidity: iris.cube.Cube | iris.cube.CubeList
551 A cube or cubelist of the relative humidity. It can
552 either be calculated or used as model output.
554 Returns
555 -------
556 iris.cube.Cube | iris.cube.CubeList
557 A cube or cubelist of the saturation precipitable water.
559 Notes
560 -----
561 The saturation precipitable water is equivalent to the precipitable
562 water assuming that the atmosphere was fully saturated.
564 It can be calculated as
566 .. math:: spw = frac{1}{\rho_w} \int \frac{w}{RH} dz
568 for spw the saturated precipitable water, ..math::\rho_{w} the density of water,
569 w the mixing ratio, RH the relative humidity (as a decimal) and z the height.
570 It is integrated from the surface to the top of the atmosphere.
572 It is applicable throughout the globe and is, perhaps, best considered
573 in relation to the precipitable water. A useful way to do this is
574 via the saturation fraction.
576 Examples
577 --------
578 >>> sat_pwat = humidity.saturated_precipitable_water(mixing_ratio, RH)
579 """
580 saturation_precipitable_water = iris.cube.CubeList([])
581 for w, rh in zip(
582 iter_maybe(mixing_ratio), iter_maybe(relative_humidity), strict=True
583 ):
584 # Integrate the data in the vertical using np.trapezoid
585 # (following trapezoid rule).
586 rh = convert_units(rh, "1")
587 spw = np.trapezoid(
588 (w / rh).data,
589 x=w.coord("level_height").points[:],
590 axis=get_cube_coordindex(w, "level_height"),
591 )
592 # Determine array information of input cube to get
593 # correct cube to copy across to.
594 if len(w.coord("realization").points) != 1 and len(w.coord("time").points) != 1:
595 satpw = w[:, :, 0, :, :].copy()
596 elif (
597 len(w.coord("realization").points) != 1 and len(w.coord("time").points) == 1
598 ):
599 satpw = w[:, 0, :, :].copy()
600 elif (
601 len(w.coord("time").points) != 1 and len(w.coord("realization").points) == 1
602 ):
603 satpw = w[:, 0, :, :].copy()
604 else:
605 satpw = w[0, :, :].copy()
606 # Store the data for output, rename cube, and correct units.
607 satpw.data = spw
608 satpw.rename("saturation_precipitable_water")
609 # Setting units to mm to account for normalization by density of water.
610 satpw.units = "mm"
611 saturation_precipitable_water.append(satpw)
612 # Output cube/cubelist.
613 if len(saturation_precipitable_water) == 1:
614 return saturation_precipitable_water[0]
615 else:
616 return saturation_precipitable_water
619def saturation_fraction(
620 mixing_ratio: iris.cube.Cube | iris.cube.CubeList,
621 relative_humidity: iris.cube.Cube | iris.cube.CubeList,
622) -> iris.cube.Cube | iris.cube.CubeList:
623 r"""Calculate saturation fraction.
625 Arguments
626 ---------
627 mixing_ratio: iris.cube.Cube | iris.cube.CubeList
628 A cube or cubelist of the mixing ratio. It can be
629 calculated within a recipe or a direct model output.
630 relative_humidity: iris.cube.Cube | iris.cube.CubeList
631 A cube or cubelist of the relative humidity. It can be
632 calculated within a recipe or used as a direct model output.
634 Returns
635 -------
636 iris.cube.Cube | iris.cube.CubeList
637 A cube or cubelist of the saturation fraction.
639 Notes
640 -----
641 The saturation fraction indicates how moist a column of the atmosphere
642 is. A value close to one implies that the atmosphere is fully saturated
643 throughout the entire column. Smaller values imply the atmosphere is
644 drier throughout the column. It is based around ideas of specific entropy
645 ([Zengetal05]_) but can be simplified to an approximation following [Daviesetal2026]_.
647 It can be approximated as
649 .. math:: saturation_fraction = \frac{precipitable_water}{saturation_precipitable_water}
651 and can be used throughout the globe with the same interpretation.
653 For a recent example, [Daviesetal2026]_ have applied the concept to their
654 conceptual model for extreme rainfall. Thus it is a potentially useful diagnostic
655 to consider for extreme events, and is thought of as more reliable than
656 using precipitable water on its own.
658 Examples
659 --------
660 >>> sf = humidity.saturation_fraction(mixing_ratio, relative_humidity)
662 References
663 ----------
664 .. [Daviesetal2026] Davies, P. A., Flack, D. L. A., Pirret, J., Fowler, H. J.
665 (2026) "Application of the Davies Four-Stage Conceptual Model for
666 Life-Threatening Rainfall Extremes on the April 2024 United Arab Emirates
667 and Oman Floods." Weather and Climate Extremes, vol. 51, 100846.
668 doi:10.1016/j.wace.2025.100846
669 .. [Zengetal05] Zeng, X., Tao, W-K, and Simpson, J. (2005) "An Equation for Moist
670 Entropy in a Precipitating and Icy Atmosphere" Journal of the Atmospheric Sciences,
671 vol. 2, 4293-4309, doi: 10.1175/JAS3570.1
672 """
673 saturation_fraction = iris.cube.CubeList([])
674 for w, rh in zip(
675 iter_maybe(mixing_ratio), iter_maybe(relative_humidity), strict=True
676 ):
677 # Calculate both precipitable water and saturation
678 # precipitable water.
679 pw = precipitable_water(w)
680 spw = saturation_precipitable_water(w, rh)
681 # Calculate the saturation fraction by taking the ratio.
682 sf = pw / spw
683 # Rename the cube and append to cubelist.
684 sf.rename("saturation_fraction")
685 saturation_fraction.append(sf)
686 # Output the cube/cubelist.
687 if len(saturation_fraction) == 1:
688 return saturation_fraction[0]
689 else:
690 return saturation_fraction