Coverage for fixtures\flow_basic_fixture.py: 100%

144 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-08 14:15 +0200

1from typing import Callable, NamedTuple 

2 

3import numpy as np 

4import pandas as pd 

5import pytest 

6import xarray as xr 

7 

8 

9def make_basic_dis(dz, nrow, ncol): 

10 """Basic model discretization""" 

11 dx = 10.0 

12 dy = -10.0 

13 

14 nlay = len(dz) 

15 

16 shape = nlay, nrow, ncol 

17 

18 xmin = 0.0 

19 xmax = dx * ncol 

20 ymin = 0.0 

21 ymax = abs(dy) * nrow 

22 dims = ("layer", "y", "x") 

23 

24 layer = np.arange(1, nlay + 1) 

25 y = np.arange(ymax, ymin, dy) + 0.5 * dy 

26 x = np.arange(xmin, xmax, dx) + 0.5 * dx 

27 coords = {"layer": layer, "y": y, "x": x} 

28 

29 ibound = xr.DataArray(np.ones(shape, dtype=np.int32), coords=coords, dims=dims) 

30 

31 surface = 0.0 

32 interfaces = np.insert((surface - np.cumsum(dz)), 0, surface) 

33 

34 bottom = xr.DataArray(interfaces[1:], coords={"layer": layer}, dims="layer") 

35 top = xr.DataArray(interfaces[:-1], coords={"layer": layer}, dims="layer") 

36 

37 return ibound, top, bottom 

38 

39 

40@pytest.fixture(scope="module") 

41def basic_dis(): 

42 return make_basic_dis(dz=[5, 30, 100], nrow=9, ncol=9) 

43 

44 

45@pytest.fixture(scope="function") 

46def basic_dis__topsystem(): 

47 return make_basic_dis(dz=[1.0, 2.0, 4.0, 10.0], nrow=9, ncol=9) 

48 

49 

50class BasicDisSettings(NamedTuple): 

51 nlay: int = 1 

52 nrow: int = 1 

53 ncol: int = 1 

54 zstart: float = 0.0 

55 zstop: float = -1.0 

56 ystart: float = 0.0 

57 ystop: float = 1.0 

58 xstart: float = 0.0 

59 xstop: float = 1.0 

60 dx: float = 1.0 

61 dy: float = 1.0 

62 dz: float = 1.0 

63 space_generator: Callable = np.linspace 

64 

65 

66@pytest.fixture 

67def parameterizable_basic_dis(request): 

68 settings = request.param 

69 shape = (settings.nlay, settings.nrow, settings.ncol) 

70 

71 x = ( 

72 settings.space_generator( 

73 settings.xstart + 1, settings.xstop + 1, settings.ncol + 1, endpoint=True 

74 ) 

75 - 1 

76 ) 

77 y = ( 

78 settings.space_generator( 

79 settings.ystop + 1, settings.ystart + 1, settings.nrow + 1, endpoint=True 

80 ) 

81 - 1 

82 ) 

83 z = ( 

84 settings.space_generator( 

85 settings.zstart - 1, settings.zstop - 1, settings.nlay + 1, endpoint=True 

86 ) 

87 + 1 

88 ) 

89 

90 xc = (x[:-1] + x[1:]) / 2 

91 yc = (y[:-1] + y[1:]) / 2 

92 

93 dx = x[1:] - x[:-1] 

94 dy = y[1:] - y[:-1] 

95 

96 layers = np.arange(settings.nlay) + 1 

97 

98 idomain = xr.DataArray( 

99 np.ones(shape, dtype=np.int32), 

100 coords={"layer": layers, "y": yc, "x": xc}, 

101 name="idomain", 

102 ) 

103 

104 # Assign dx and dy coordinates. They are needed for certain methods like 'coord_reference' 

105 if np.all(np.isclose(dx, dx[0])): 

106 idomain = idomain.assign_coords({"dx": dx[0]}) 

107 else: 

108 idomain = idomain.assign_coords({"dx": ("x", dx)}) 

109 if np.all(np.isclose(dy, dy[0])): 

110 idomain = idomain.assign_coords({"dy": dy[0]}) 

111 else: 

112 idomain = idomain.assign_coords({"dy": ("y", dy)}) 

113 

114 top = xr.DataArray(z[:-1], coords={"layer": layers}) 

115 bottom = xr.DataArray(z[1:], coords={"layer": layers}) 

116 

117 return idomain, top, bottom 

118 

119 

120@pytest.fixture(scope="module") 

121def three_days(): 

122 """Simple time discretization of three days""" 

123 return pd.date_range(start="2018-01-01", end="2018-01-03", freq="D") 

124 

125 

126@pytest.fixture(scope="module") 

127def two_days(): 

128 """Simple time discretization of two days""" 

129 return pd.date_range(start="2018-01-01", end="2018-01-02", freq="D") 

130 

131 

132@pytest.fixture(scope="module") 

133def well_df(three_days): 

134 nrow = 9 

135 ncol = 9 

136 dx = 10.0 

137 dy = -10.0 

138 xmin = 0.0 

139 xmax = dx * ncol 

140 ymin = 0.0 

141 ymax = abs(dy) * nrow 

142 

143 y = np.arange(ymax, ymin, dy) + 0.5 * dy 

144 x = np.arange(xmin, xmax, dx) + 0.5 * dx 

145 

146 times = three_days 

147 n_repeats = int(len(x) / len(times)) + 1 

148 

149 df = pd.DataFrame() 

150 df["id_name"] = np.arange(len(x)).astype(str) 

151 df["x"] = x 

152 df["y"] = y 

153 df["rate"] = dx * dy * -1 * 0.5 

154 df["time"] = np.tile(times, n_repeats)[: len(x)] 

155 df["layer"] = 2 

156 return df 

157 

158 

159@pytest.fixture(scope="module") 

160def get_render_dict(): 

161 """ 

162 Helper function to return dict to render. 

163 

164 Fixture returns local helper function, so that the helper function 

165 is only evaluated when called. 

166 See: https://stackoverflow.com/a/51389067 

167 """ 

168 

169 def _get_render_dict(package, directory, globaltimes, nlayer, system_index=1): 

170 composition = package.compose( 

171 directory, 

172 globaltimes, 

173 nlayer, 

174 system_index=system_index, 

175 ) 

176 

177 return { 

178 "pkg_id": package._pkg_id, 

179 "name": package.__class__.__name__, 

180 "variable_order": package._variable_order, 

181 "package_data": composition[package._pkg_id], 

182 } 

183 

184 return _get_render_dict 

185 

186 

187@pytest.fixture(scope="module") 

188def metaswap_dict(basic_dis): 

189 ibound, _, _ = basic_dis 

190 

191 active = ibound.isel(layer=0) 

192 

193 d = {} 

194 d["boundary"] = active 

195 d["landuse"] = active 

196 d["rootzone_thickness"] = 1.2 

197 d["soil_physical_unit"] = active 

198 d["meteo_station_number"] = active 

199 d["surface_elevation"] = 0.0 

200 d["sprinkling_type"] = active 

201 d["sprinkling_layer"] = active 

202 d["sprinkling_capacity"] = 1000.0 

203 d["wetted_area"] = 30.0 

204 d["urban_area"] = 30.0 

205 d["ponding_depth_urban"] = 0.02 

206 d["ponding_depth_rural"] = 0.005 

207 d["runoff_resistance_urban"] = 1.5 

208 d["runoff_resistance_rural"] = 1.5 

209 d["runon_resistance_urban"] = 1.5 

210 d["runon_resistance_rural"] = 1.5 

211 d["infiltration_capacity_urban"] = 10.0 

212 d["infiltration_capacity_rural"] = 2.0 

213 d["perched_water_table"] = 0.5 

214 d["soil_moisture_factor"] = 1.0 

215 d["conductivity_factor"] = 1.0 

216 

217 d["lookup_and_forcing_files"] = [ 

218 "fact_svat.inp", 

219 "luse_svat.inp", 

220 "mete_grid.inp", 

221 "para_sim.inp", 

222 "tiop_sim.inp", 

223 "init_svat.inp", 

224 "comp_post.inp", 

225 "sel_key_svat_per.inp", 

226 ] 

227 

228 return d 

229 

230 

231@pytest.fixture(scope="module") 

232def horizontal_flow_barrier_gdf(basic_dis): 

233 """GeoDataframe that can be used to initiate HorizontalFlowBarriers""" 

234 import geopandas as gpd 

235 from shapely.geometry import LineString 

236 

237 ibound, _, _ = basic_dis 

238 

239 x = ibound.x.values 

240 y = ibound.y.values 

241 

242 line1 = LineString([(x[1], y[1]), (x[1], y[-2])]) 

243 line2 = LineString([(x[4], y[1]), (x[4], y[-2])]) 

244 

245 lines = np.array([line1, line2, line1, line2], dtype="object") 

246 hfb_layers = np.array([1, 1, 3, 3]) 

247 id_name = ["left_upper", "right_upper", "left_lower", "right_lower"] 

248 

249 hfb_gdf = gpd.GeoDataFrame( 

250 geometry=lines, 

251 data={"layer": hfb_layers, "resistance": 100.0, "id_name": id_name}, 

252 ) 

253 

254 return hfb_gdf