Coverage for fixtures\flow_basic_fixture.py: 100%

1 from typing import Callable, NamedTuple

2

3 import numpy as np

4 import pandas as pd

5 import pytest

6 import xarray as xr

7

8

9 def 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")

41 def basic_dis():

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

43

44

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

46 def basic_dis__topsystem():

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

48

49

50 class 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

67 def 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")

121 def 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")

127 def 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")

133 def 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")

160 def 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")

188 def 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")

232 def 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