Coverage for src / CSET / operators / humidity.py: 0%
84 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-02 15:01 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-02 15:01 +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.constants import EPSILON
21from CSET.operators.pressure import vapour_pressure
24def specific_humidity_to_mixing_ratio(
25 cubes: iris.cube.Cube | iris.cube.CubeList,
26) -> iris.cube.Cube | iris.cube.CubeList:
27 """Convert specific humidity to mixing ratio."""
28 w = iris.cube.CubeList([])
29 for cube in iter_maybe(cubes):
30 mr = cube.copy()
31 mr = cube / (1 - cube)
32 mr.rename("mixing_ratio")
33 w.append(mr)
34 if len(w) == 1:
35 return w[0]
36 else:
37 return w
40def mixing_ratio_to_specific_humidity(
41 cubes: iris.cube.Cube | iris.cube.CubeList,
42) -> iris.cube.Cube | iris.cube.CubeList:
43 """Convert mixing ratio to specific humidity."""
44 q = iris.cube.CubeList([])
45 for cube in iter_maybe(cubes):
46 sh = cube.copy()
47 sh = cube / (1 + cube)
48 sh.rename("specific_humidity")
49 q.append(sh)
50 if len(q) == 1:
51 return q[0]
52 else:
53 return q
56def saturation_mixing_ratio(
57 temperature: iris.cube.Cube | iris.cube.CubeList,
58 pressure: iris.cube.Cube | iris.cube.CubeList,
59) -> iris.cube.Cube | iris.cube.CubeList:
60 """Calculate saturation mixing ratio."""
61 w = iris.cube.CubeList([])
62 for T, P in zip(iter_maybe(temperature), iter_maybe(pressure), strict=True):
63 mr = (EPSILON * vapour_pressure(T)) / ((P / 100.0) - vapour_pressure(T))
64 mr.units("kg/kg")
65 mr.rename("mixing_ratio")
66 w.append(mr)
67 if len(w) == 1:
68 return w[0]
69 else:
70 return w
73def saturation_specific_humidity(
74 temperature: iris.cube.Cube | iris.cube.CubeList,
75 pressure: iris.cube.Cube | iris.cube.CubeList,
76) -> iris.cube.Cube | iris.cube.CubeList:
77 """Calculate saturation specific humidity."""
78 q = iris.cube.CubeList([])
79 for T, P in zip(iter_maybe(temperature), iter_maybe(pressure), strict=True):
80 sh = (EPSILON * vapour_pressure(T)) / (P / 100.0)
81 sh.units("kg/kg")
82 sh.rename("specific_humidity")
83 q.append(sh)
84 if len(q) == 1:
85 return q[0]
86 else:
87 return q
90def mixing_ratio_from_RH(
91 temperature: iris.cube.Cube | iris.cube.CubeList,
92 pressure: iris.cube.Cube | iris.cube.CubeList,
93 relative_humidity: iris.cube.Cube | iris.cube.CubeList,
94) -> iris.cube.Cube | iris.cube.CubeList:
95 """Calculate the mixing ratio from RH."""
96 w = iris.cube.CubeList([])
97 for T, P, RH in zip(
98 iter_maybe(temperature),
99 iter_maybe(pressure),
100 iter_maybe(relative_humidity),
101 strict=True,
102 ):
103 if RH.units == "%":
104 RH /= 100.0
105 RH.units = "1"
106 mr = saturation_mixing_ratio(T, P) * RH
107 w.append(mr)
108 if len(w) == 1:
109 return w[0]
110 else:
111 return w
114def specific_humidity_from_RH(
115 temperature: iris.cube.Cube | iris.cube.CubeList,
116 pressure: iris.cube.Cube | iris.cube.CubeList,
117 relative_humidity: iris.cube.Cube | iris.cube.CubeList,
118) -> iris.cube.Cube | iris.cube.CubeList:
119 """Calculate the mixing ratio from RH."""
120 q = iris.cube.CubeList([])
121 for T, P, RH in zip(
122 iter_maybe(temperature),
123 iter_maybe(pressure),
124 iter_maybe(relative_humidity),
125 strict=True,
126 ):
127 if RH.units == "%":
128 RH /= 100.0
129 RH.units = "1"
130 sh = saturation_specific_humidity(T, P) * RH
131 q.append(sh)
132 if len(q) == 1:
133 return q[0]
134 else:
135 return q
138def relative_humidity_from_mixing_ratio(
139 mixing_ratio: iris.cube.Cube | iris.cube.CubeList,
140 temperature: iris.cube.Cube | iris.cube.CubeList,
141 pressure: iris.cube.Cube | iris.cube.CubeList,
142) -> iris.cube.Cube | iris.cube.CubeList:
143 """Convert mixing ratio to relative humidity."""
144 RH = iris.cube.CubeList([])
145 for W, T, P in zip(
146 iter_maybe(mixing_ratio),
147 iter_maybe(temperature),
148 iter_maybe(pressure),
149 strict=True,
150 ):
151 rel_h = W / saturation_mixing_ratio(T, P)
152 rel_h.rename("relative_humidity")
153 RH.append(rel_h)
154 if len(RH) == 1:
155 return RH[0]
156 else:
157 return RH
160def relative_humidity_from_specific_humidity(
161 specific_humidity: iris.cube.Cube | iris.cube.CubeList,
162 temperature: iris.cube.Cube | iris.cube.CubeList,
163 pressure: iris.cube.Cube | iris.cube.CubeList,
164) -> iris.cube.Cube | iris.cube.CubeList:
165 """Convert specific humidity to relative humidity."""
166 RH = iris.cube.CubeList([])
167 for Q, T, P in zip(
168 iter_maybe(specific_humidity),
169 iter_maybe(temperature),
170 iter_maybe(pressure),
171 strict=True,
172 ):
173 rel_h = Q / saturation_specific_humidity(T, P)
174 rel_h.rename("relative_humidity")
175 RH.append(rel_h)
176 if len(RH) == 1:
177 return RH[0]
178 else:
179 return RH