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

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.constants import EPSILON 

21from CSET.operators.pressure import vapour_pressure 

22 

23 

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 

38 

39 

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 

54 

55 

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 

71 

72 

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 

88 

89 

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 

112 

113 

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 

136 

137 

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 

158 

159 

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