Coverage for fixtures\mf6_circle_fixture.py: 52%

1 import copy

2

3 import numpy as np

4 import pandas as pd

5 import pytest

6 import xarray as xr

7 import xugrid as xu

8

9 import imod

10

11

12 def make_circle_model():

13 grid = imod.data.circle()

14 nface = grid.n_face

15

16 nlayer = 2

17

18 idomain = xu.UgridDataArray(

19 xr.DataArray(

20 np.ones((nlayer, nface), dtype=np.int32),

21 coords={"layer": [1, 2]},

22 dims=["layer", grid.face_dimension],

23 ),

24 grid=grid,

25 )

26 icelltype = xu.full_like(idomain, 0)

27 k = xu.full_like(idomain, 1.0, dtype=np.float64)

28 k33 = k.copy()

29 rch_rate = xu.full_like(k.sel(layer=1), 0.001, dtype=float)

30

31 bottom = k * xr.DataArray([5.0, 0.0], dims=["layer"])

32 chd_location = xu.zeros_like(k.sel(layer=2), dtype=bool).ugrid.binary_dilation(

33 border_value=True

34 )

35 constant_head = xu.full_like(k.sel(layer=2), 1.0).where(chd_location)

36

37 gwf_model = imod.mf6.GroundwaterFlowModel()

38 gwf_model["disv"] = imod.mf6.VerticesDiscretization(

39 top=10.0, bottom=bottom, idomain=idomain

40 )

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

42 constant_head, print_input=True, print_flows=True, save_flows=True

43 )

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

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

46 icelltype=icelltype,

47 k=k,

48 k33=k33,

49 save_flows=True,

50 )

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

52 specific_storage=1.0e-5,

53 specific_yield=0.15,

54 transient=False,

55 convertible=0,

56 save_flows=False,

57 )

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

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

60

61 simulation = imod.mf6.Modflow6Simulation("circle")

62 simulation["GWF_1"] = gwf_model

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

64 modelnames=["GWF_1"],

65 print_option="summary",

66 csv_output=False,

67 no_ptc=True,

68 outer_dvclose=1.0e-4,

69 outer_maximum=500,

70 under_relaxation=None,

71 inner_dvclose=1.0e-4,

72 inner_rclose=0.001,

73 inner_maximum=100,

74 linear_acceleration="cg",

75 scaling_method=None,

76 reordering_method=None,

77 relaxation_factor=0.97,

78 )

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

80 return simulation

81

82

83 def make_circle_model_flow_with_transport_data(species: list[str]):

84 grid = imod.data.circle()

85 max_concentration = 35.0

86 min_concentration = 0.0

87 nface = grid.n_face

88

89 nlayer = 2

90

91 idomain = xu.UgridDataArray(

92 xr.DataArray(

93 np.ones((nlayer, nface), dtype=np.int32),

94 coords={"layer": [1, 2]},

95 dims=["layer", grid.face_dimension],

96 ),

97 grid=grid,

98 )

99 icelltype = xu.full_like(idomain, 0)

100 k = xu.full_like(idomain, 1.0, dtype=np.float64)

101 k33 = k.copy()

102 rch_rate = xu.full_like(k.sel(layer=1), 0.001, dtype=float)

103 rch_concentration = xu.full_like(rch_rate, min_concentration)

104 rch_concentration = rch_concentration.expand_dims(species=species)

105 bottom = k * xr.DataArray([5.0, 0.0], dims=["layer"])

106 chd_location = xu.zeros_like(k.sel(layer=2), dtype=bool).ugrid.binary_dilation(

107 border_value=True

108 )

109 constant_head = xu.full_like(k.sel(layer=2), 1.0).where(chd_location)

110 constant_concentration = xu.full_like(constant_head, max_concentration).where(

111 chd_location

112 )

113 constant_concentration = constant_concentration.expand_dims(species=species)

114

115 gwf_model = imod.mf6.GroundwaterFlowModel(save_flows=True)

116 gwf_model["disv"] = imod.mf6.VerticesDiscretization(

117 top=10.0, bottom=bottom, idomain=idomain

118 )

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

120 constant_head,

121 concentration=constant_concentration,

122 print_input=True,

123 print_flows=True,

124 save_flows=True,

125 )

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

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

128 icelltype=icelltype,

129 k=k,

130 k33=k33,

131 save_flows=True,

132 )

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

134 specific_storage=1.0e-5,

135 specific_yield=0.15,

136 transient=False,

137 convertible=0,

138 save_flows=False,

139 )

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

141 gwf_model["rch"] = imod.mf6.Recharge(

142 rch_rate, save_flows=True, concentration=rch_concentration

143 )

144

145 simulation = imod.mf6.Modflow6Simulation("circle")

146 simulation["GWF_1"] = gwf_model

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

148 modelnames=["GWF_1"],

149 print_option="summary",

150 csv_output=False,

151 no_ptc=True,

152 outer_dvclose=1.0e-4,

153 outer_maximum=500,

154 under_relaxation=None,

155 inner_dvclose=1.0e-4,

156 inner_rclose=0.001,

157 inner_maximum=100,

158 linear_acceleration="cg",

159 scaling_method=None,

160 reordering_method=None,

161 relaxation_factor=0.97,

162 )

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

164 return simulation

165

166

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

168 def circle_model():

169 return make_circle_model()

170

171

172@pytest.mark.usefixtures("circle_model")

173@pytest.fixture(scope="session")

174 def circle_result(tmpdir_factory):

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

176 # directory between different testing modules.

177 modeldir = tmpdir_factory.mktemp("circle")

178 simulation = make_circle_model()

179 simulation.write(modeldir)

180 simulation.run()

181 return modeldir

182

183

184 def make_circle_model_evt():

185 simulation = make_circle_model()

186 gwf_model = simulation["GWF_1"]

187

188 idomain = gwf_model["disv"].dataset["idomain"]

189 like = idomain.sel(layer=1).astype(np.float64)

190 face_dim = idomain.ugrid.grid.face_dimension

191

192 rate = xu.full_like(like, 0.001)

193 # Lay surface on chd level

194 surface = xu.full_like(like, 1.0)

195 depth = xu.full_like(like, 2.0)

196

197 segments = xr.DataArray(

198 data=[1, 2, 3], coords={"segment": [1, 2, 3]}, dims=("segment",)

199 )

200 segments_reversed = segments.copy()

201 segments_reversed.values = [3, 2, 1]

202

203 proportion_depth = xu.full_like(like, 0.3) * segments

204 proportion_rate = xu.full_like(like, 0.3) * segments_reversed

205

206 proportion_depth = proportion_depth.transpose("segment", face_dim)

207 proportion_rate = proportion_rate.transpose("segment", face_dim)

208

209 gwf_model["evt"] = imod.mf6.Evapotranspiration(

210 surface, rate, depth, proportion_rate, proportion_depth

211 )

212

213 simulation["GWF_1"] = gwf_model

214

215 return simulation

216

217

218@pytest.fixture(scope="session")

219 def circle_model_evt():

220 return make_circle_model_evt()

221

222

223@pytest.mark.usefixtures("circle_model_evt")

224@pytest.fixture(scope="session")

225 def circle_result_evt(tmpdir_factory):

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

227 # directory between different testing modules.

228 modeldir = tmpdir_factory.mktemp("circle_evt")

229 simulation = make_circle_model_evt()

230 simulation.write(modeldir)

231 simulation.run()

232 return modeldir

233

234

235 def make_circle_model_save_sto():

236 simulation = make_circle_model()

237 gwf_model = simulation["GWF_1"]

238

239 gwf_model["sto"].dataset["save_flows"] = True

240 return simulation

241

242

243@pytest.fixture(scope="session")

244 def circle_result_sto(tmpdir_factory):

245 """

246 Circle result with storage fluxes, which are saved as METH1 instead of METH6

247 """

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

249 # directory between different testing modules.

250 modeldir = tmpdir_factory.mktemp("circle_sto")

251 simulation = make_circle_model_save_sto()

252 simulation.write(modeldir)

253 simulation.run()

254 return modeldir

255

256

257@pytest.mark.usefixtures("circle_model_evt")

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

259 def circle_partitioned():

260 simulation = make_circle_model_evt()

261

262 idomain = simulation["GWF_1"]["disv"].dataset["idomain"]

263 submodel_labels = copy.deepcopy(idomain.sel({"layer": 1}))

264

265 submodel_labels.values[:67] = 0

266 submodel_labels.values[67:118] = 1

267 submodel_labels.values[118:] = 2

268

269 return simulation.split(submodel_labels)

270

271

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

273 def circle_model_transport():

274 al = 0.001

275 porosity = 0.3

276 max_concentration = 35.0

277 min_concentration = 0.0

278 max_density = 1025.0

279 min_density = 1000.0

280

281 simulation = make_circle_model_flow_with_transport_data(["salinity"])

282 gwf_model = simulation["GWF_1"]

283

284 slope = (max_density - min_density) / (max_concentration - min_concentration)

285 gwf_model["buoyancy"] = imod.mf6.Buoyancy(

286 reference_density=min_density,

287 modelname=["transport"],

288 reference_concentration=[min_concentration],

289 density_concentration_slope=[slope],

290 species=["salinity"],

291 )

292 transport_model = imod.mf6.GroundwaterTransportModel(save_flows=True)

293 transport_model["ssm"] = imod.mf6.SourceSinkMixing.from_flow_model(

294 gwf_model, "salinity", save_flows=True

295 )

296 transport_model["disv"] = gwf_model["disv"]

297

298 # %%

299 # Now we define some transport packages for simulating the physical processes

300 # of advection, mechanical dispersion, and molecular diffusion dispersion. This

301 # example is transient, and the volume available for storage is the porosity,

302 # in this case 0.10.

303

304 transport_model["dsp"] = imod.mf6.Dispersion(

305 diffusion_coefficient=1e-4,

306 longitudinal_horizontal=al,

307 transversal_horizontal1=al * 0.1,

308 transversal_vertical=al * 0.01,

309 xt3d_off=False,

310 xt3d_rhs=False,

311 )

312 transport_model["adv"] = imod.mf6.AdvectionUpstream()

313 transport_model["mst"] = imod.mf6.MobileStorageTransfer(porosity, save_flows=True)

314

315 # %%

316 # Define the maximum concentration as the initial conditions, also output

317 # options for the transport model, and assign the transport model to the

318 # simulation as well.

319 max_concentration = 35.0

320 min_concentration = 0.0

321 transport_model["ic"] = imod.mf6.InitialConditions(start=max_concentration)

322 transport_model["oc"] = imod.mf6.OutputControl(

323 save_concentration="last", save_budget="last"

324 )

325

326 simulation["transport"] = transport_model

327 simulation["transport_solver"] = imod.mf6.Solution(

328 modelnames=["transport"],

329 print_option="summary",

330 csv_output=False,

331 no_ptc=True,

332 outer_dvclose=1.0e-4,

333 outer_maximum=500,

334 under_relaxation=None,

335 inner_dvclose=1.0e-4,

336 inner_rclose=0.001,

337 inner_maximum=100,

338 linear_acceleration="bicgstab",

339 scaling_method=None,

340 reordering_method=None,

341 relaxation_factor=0.97,

342 )

343 simtimes = pd.date_range(start="2000-01-01", end="2001-01-01", freq="W")

344 simulation.create_time_discretization(additional_times=simtimes)

345 return simulation

346

347

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

349 def circle_model_transport_multispecies_variable_density():

350 al = 0.001

351 porosity = 0.3

352 max_concentration = 35.0

353 min_concentration = 0.0

354 max_density = 1025.0

355 min_density = 1000.0

356 species = ["salt", "temp"]

357

358 simulation = make_circle_model_flow_with_transport_data(species)

359 gwf_model = simulation["GWF_1"]

360

361 for specie in species:

362 transport_model = imod.mf6.GroundwaterTransportModel(save_flows=True)

363 transport_model["ssm"] = imod.mf6.SourceSinkMixing.from_flow_model(

364 gwf_model, specie, save_flows=True

365 )

366 transport_model["disv"] = gwf_model["disv"]

367

368 # %%

369 # Now we define some transport packages for simulating the physical processes

370 # of advection, mechanical dispersion, and molecular diffusion dispersion. This

371 # example is transient, and the volume available for storage is the porosity,

372 # in this case 0.10.

373

374 transport_model["dsp"] = imod.mf6.Dispersion(

375 diffusion_coefficient=1e-4,

376 longitudinal_horizontal=al,

377 transversal_horizontal1=al * 0.1,

378 transversal_vertical=al * 0.01,

379 xt3d_off=False,

380 xt3d_rhs=False,

381 )

382 transport_model["adv"] = imod.mf6.AdvectionUpstream()

383 transport_model["mst"] = imod.mf6.MobileStorageTransfer(

384 porosity, save_flows=True

385 )

386

387 # %% Define the maximum concentration as the initial conditions, also

388 # output options for the transport model, and assign the transport model

389 # to the simulation as well.

390 transport_model["ic"] = imod.mf6.InitialConditions(start=max_concentration)

391 transport_model["oc"] = imod.mf6.OutputControl(

392 save_concentration="all", save_budget="all"

393 )

394

395 simulation[f"tpt_{specie}"] = transport_model

396 slope = (max_density - min_density) / (max_concentration - min_concentration)

397 modelnames = [f"tpt_{specie}" for specie in species]

398 gwf_model["buoyancy"] = imod.mf6.Buoyancy(

399 reference_density=min_density,

400 modelname=modelnames,

401 reference_concentration=[min_concentration, min_concentration],

402 density_concentration_slope=[slope, slope],

403 species=species,

404 )

405

406 simulation["transport_solver"] = imod.mf6.Solution(

407 modelnames=modelnames,

408 print_option="summary",

409 csv_output=False,

410 no_ptc=True,

411 outer_dvclose=1.0e-4,

412 outer_maximum=500,

413 under_relaxation=None,

414 inner_dvclose=1.0e-4,

415 inner_rclose=0.001,

416 inner_maximum=100,

417 linear_acceleration="bicgstab",

418 scaling_method=None,

419 reordering_method=None,

420 relaxation_factor=0.97,

421 )

422 simtimes = pd.date_range(start="2000-01-01", end="2001-01-01", freq="W")

423 simulation.create_time_discretization(additional_times=simtimes)

424 return simulation