Coverage for fixtures\mf6_twri_fixture.py: 100%

1 import numpy as np

2 import pandas as pd

3 import pytest

4 import xarray as xr

5

6 import imod

7 from imod.typing.grid import zeros_like

8

9

10 def make_twri_model():

11 nlay = 3

12 nrow = 15

13 ncol = 15

14 shape = (nlay, nrow, ncol)

15

16 dx = 5000.0

17 dy = -5000.0

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.array([1, 2, 3])

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 # Discretization data

30 like = xr.DataArray(np.ones(shape), coords=coords, dims=dims)

31 idomain = like.astype(np.int8)

32 bottom = xr.DataArray([-200.0, -300.0, -450.0], {"layer": layer}, ("layer",))

33

34 # Constant head

35 head = xr.full_like(like, np.nan).sel(layer=[1, 2])

36 head[..., 0] = 0.0

37

38 # Drainage

39 elevation = xr.full_like(like.sel(layer=1), np.nan)

40 conductance = xr.full_like(like.sel(layer=1), np.nan)

41 elevation[7, 1:10] = np.array([0.0, 0.0, 10.0, 20.0, 30.0, 50.0, 70.0, 90.0, 100.0])

42 conductance[7, 1:10] = 1.0

43

44 # Node properties

45 icelltype = xr.DataArray([1, 0, 0], {"layer": layer}, ("layer",))

46 k = xr.DataArray([1.0e-3, 1.0e-4, 2.0e-4], {"layer": layer}, ("layer",))

47 k33 = xr.DataArray([2.0e-8, 2.0e-8, 2.0e-8], {"layer": layer}, ("layer",))

48

49 # Recharge

50 rch_rate = xr.full_like(like.sel(layer=1), 3.0e-8)

51

52 # Well

53 wells_x = [

54 52500.0,

55 27500.0,

56 57500.0,

57 37500.0,

58 47500.0,

59 57500.0,

60 67500.0,

61 37500.0,

62 47500.0,

63 57500.0,

64 67500.0,

65 37500.0,

66 47500.0,

67 57500.0,

68 67500.0,

69 ]

70 wells_y = [

71 52500.0,

72 57500.0,

73 47500.0,

74 32500.0,

75 32500.0,

76 32500.0,

77 32500.0,

78 22500.0,

79 22500.0,

80 22500.0,

81 22500.0,

82 12500.0,

83 12500.0,

84 12500.0,

85 12500.0,

86 ]

87 screen_top = [

88 -300.0,

89 -200.0,

90 -200.0,

91 200.0,

92 200.0,

93 200.0,

94 200.0,

95 200.0,

96 200.0,

97 200.0,

98 200.0,

99 200.0,

100 200.0,

101 200.0,

102 200.0,

103 ]

104 screen_bottom = [

105 -450.0,

106 -300.0,

107 -300.0,

108 -200.0,

109 -200.0,

110 -200.0,

111 -200.0,

112 -200.0,

113 -200.0,

114 -200.0,

115 -200.0,

116 -200.0,

117 -200.0,

118 -200.0,

119 -200.0,

120 ]

121 rate_wel = [

122 -5.0,

123 -5.0,

124 -5.0,

125 -5.0,

126 -5.0,

127 -5.0,

128 -5.0,

129 -5.0,

130 -5.0,

131 -5.0,

132 -5.0,

133 -5.0,

134 -5.0,

135 -5.0,

136 -5.0,

137 ]

138

139 # Create and fill the groundwater model.

140 gwf_model = imod.mf6.GroundwaterFlowModel()

141 gwf_model["wel"] = imod.mf6.Well(

142 x=wells_x,

143 y=wells_y,

144 screen_top=screen_top,

145 screen_bottom=screen_bottom,

146 rate=rate_wel,

147 minimum_k=1e-19,

148 save_flows=True,

149 )

150 gwf_model["dis"] = imod.mf6.StructuredDiscretization(

151 top=200.0, bottom=bottom, idomain=idomain

152 )

153 gwf_model["chd"] = imod.mf6.ConstantHead(

154 head, print_input=True, print_flows=True, save_flows=True

155 )

156 gwf_model["drn"] = imod.mf6.Drainage(

157 elevation=elevation,

158 conductance=conductance,

159 print_input=True,

160 print_flows=True,

161 save_flows=True,

162 )

163 gwf_model["ic"] = imod.mf6.InitialConditions(start=0.0)

164 gwf_model["npf"] = imod.mf6.NodePropertyFlow(

165 icelltype=icelltype,

166 k=k,

167 k33=k33,

168 variable_vertical_conductance=True,

169 dewatered=True,

170 perched=True,

171 save_flows=True,

172 )

173 gwf_model["oc"] = imod.mf6.OutputControl(save_head="all", save_budget="all")

174 gwf_model["rch"] = imod.mf6.Recharge(rch_rate)

175

176 gwf_model["sto"] = imod.mf6.SpecificStorage(

177 specific_storage=1.0e-15,

178 specific_yield=0.15,

179 convertible=0,

180 transient=False,

181 )

182

183 # The mere presence of the api package should not impact results. It

184 # should not prevent model execution, splitting or partitioning.

185 gwf_model["api"] = imod.mf6.ApiPackage(maxbound=20)

186

187 # Attach it to a simulation

188 simulation = imod.mf6.Modflow6Simulation("ex01-twri")

189 simulation["GWF_1"] = gwf_model

190 # Define solver settings

191 simulation["solver"] = imod.mf6.Solution(

192 modelnames=["GWF_1"],

193 print_option="summary",

194 outer_dvclose=1.0e-4,

195 outer_maximum=500,

196 under_relaxation=None,

197 inner_dvclose=1.0e-4,

198 inner_rclose=0.001,

199 inner_maximum=100,

200 linear_acceleration="cg",

201 scaling_method=None,

202 reordering_method=None,

203 relaxation_factor=0.97,

204 )

205 # Collect time discretization

206 simulation.create_time_discretization(additional_times=["2000-01-01", "2000-01-02"])

207 return simulation

208

209

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

211 def twri_model():

212 """Returns steady-state confined model."""

213 return make_twri_model()

214

215

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

217 def transient_twri_model():

218 """Returns transient confined model."""

219 simulation = make_twri_model()

220 gwf_model = simulation["GWF_1"]

221 like = gwf_model["dis"]["idomain"].astype(float)

222 gwf_model["sto"] = imod.mf6.SpecificStorage(

223 specific_storage=xr.full_like(like, 1.0e-15),

224 specific_yield=xr.full_like(like, 0.15),

225 convertible=0,

226 transient=True,

227 save_flows=True,

228 )

229 simulation.create_time_discretization(

230 additional_times=pd.date_range("2000-01-01", " 2000-01-03")

231 )

232 return simulation

233

234

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

236 def transient_unconfined_twri_model():

237 """Returns transient unconfined model, also saves specific discharges."""

238 simulation = make_twri_model()

239 gwf_model = simulation["GWF_1"]

240 like = gwf_model["dis"]["idomain"].astype(float)

241 # Force storage to unconfined

242 gwf_model["sto"] = imod.mf6.SpecificStorage(

243 specific_storage=xr.full_like(like, 1.0e-15),

244 specific_yield=xr.full_like(like, 0.15),

245 convertible=1,

246 transient=True,

247 save_flows=True,

248 )

249 # Force npf to unconfined

250 layer = np.array([1, 2, 3])

251 icelltype = xr.DataArray([1, 1, 1], {"layer": layer}, ("layer",))

252 gwf_model["npf"]["icelltype"] = icelltype

253 # Store save cell saturation

254 gwf_model["npf"]["save_saturation"] = True

255 # Write specific discharges

256 gwf_model["npf"]["save_specific_discharge"] = True

257 simulation.create_time_discretization(

258 additional_times=pd.date_range("2000-01-01", " 2000-01-03")

259 )

260 return simulation

261

262

263@pytest.mark.usefixtures("twri_model")

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

265 def twri_result(tmpdir_factory):

266 # Using a tmpdir_factory is the canonical way of sharing a tempory pytest

267 # directory between different testing modules.

268 modeldir = tmpdir_factory.mktemp("ex01-twri")

269 simulation = make_twri_model()

270 simulation.write(modeldir)

271 simulation.run()

272 return modeldir

273

274

275@pytest.mark.usefixtures("transient_twri_model")

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

277 def transient_twri_result(tmpdir_factory, transient_twri_model):

278 # Using a tmpdir_factory is the canonical way of sharing a tempory pytest

279 # directory between different testing modules.

280 modeldir = tmpdir_factory.mktemp("ex01-twri-transient")

281 simulation = transient_twri_model

282 simulation.write(modeldir)

283 simulation.run()

284 return modeldir

285

286

287@pytest.mark.usefixtures("transient_unconfined_twri_model")

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

289 def transient_unconfined_twri_result(tmpdir_factory, transient_unconfined_twri_model):

290 # Using a tmpdir_factory is the canonical way of sharing a tempory pytest

291 # directory between different testing modules.

292 modeldir = tmpdir_factory.mktemp("ex01-twri-transient-unconfined")

293 simulation = transient_unconfined_twri_model

294 simulation.write(modeldir)

295 simulation.run()

296 return modeldir

297

298

299@pytest.mark.usefixtures("transient_twri_model")

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

301 def split_transient_twri_model(transient_twri_model):

302 active = transient_twri_model["GWF_1"].domain.sel(layer=1)

303 number_partitions = 3

304 split_location = np.linspace(active.y.min(), active.y.max(), number_partitions + 1)

305

306 coords = active.coords

307 submodel_labels = zeros_like(active)

308 for id in np.arange(1, number_partitions):

309 submodel_labels.loc[

310 (coords["y"] > split_location[id]) & (coords["y"] <= split_location[id + 1])

311 ] = id

312

313 split_simulation = transient_twri_model.split(submodel_labels)

314

315 return split_simulation