|
| 1 | +import sys |
| 2 | +from pathlib import Path |
| 3 | +from pprint import pformat |
| 4 | +from tempfile import TemporaryDirectory |
| 5 | + |
| 6 | +import numpy as np |
| 7 | +import xarray as xr |
| 8 | + |
| 9 | +import flopy |
| 10 | + |
| 11 | +print(sys.version) |
| 12 | +print(f"flopy version: {flopy.__version__}") |
| 13 | + |
| 14 | +DNODATA = 3.0e30 |
| 15 | + |
| 16 | + |
| 17 | +def create_sim(ws): |
| 18 | + name = "uzf01" |
| 19 | + perlen = [500.0] |
| 20 | + nper = len(perlen) |
| 21 | + nstp = [10] |
| 22 | + tsmult = nper * [1.0] |
| 23 | + crs = "EPSG:26916" |
| 24 | + nlay, nrow, ncol = 100, 1, 1 |
| 25 | + delr = 1.0 |
| 26 | + delc = 1.0 |
| 27 | + delv = 1.0 |
| 28 | + top = 100.0 |
| 29 | + botm = [top - (k + 1) * delv for k in range(nlay)] |
| 30 | + strt = 0.5 |
| 31 | + hk = 1.0 |
| 32 | + laytyp = 1 |
| 33 | + ss = 0.0 |
| 34 | + sy = 0.1 |
| 35 | + |
| 36 | + tdis_rc = [] |
| 37 | + for i in range(nper): |
| 38 | + tdis_rc.append((perlen[i], nstp[i], tsmult[i])) |
| 39 | + |
| 40 | + # build MODFLOW 6 files |
| 41 | + sim = flopy.mf6.MFSimulation( |
| 42 | + sim_name=name, version="mf6", exe_name="mf6", sim_ws=ws |
| 43 | + ) |
| 44 | + |
| 45 | + # create tdis package |
| 46 | + tdis = flopy.mf6.ModflowTdis(sim, time_units="DAYS", nper=nper, perioddata=tdis_rc) |
| 47 | + |
| 48 | + # create iterative model solution and register the gwf model with it |
| 49 | + nouter, ninner = 100, 10 |
| 50 | + hclose, rclose, relax = 1.5e-6, 1e-6, 0.97 |
| 51 | + imsgwf = flopy.mf6.ModflowIms( |
| 52 | + sim, |
| 53 | + print_option="SUMMARY", |
| 54 | + outer_dvclose=hclose, |
| 55 | + outer_maximum=nouter, |
| 56 | + under_relaxation="DBD", |
| 57 | + under_relaxation_theta=0.7, |
| 58 | + inner_maximum=ninner, |
| 59 | + inner_dvclose=hclose, |
| 60 | + rcloserecord=rclose, |
| 61 | + linear_acceleration="BICGSTAB", |
| 62 | + scaling_method="NONE", |
| 63 | + reordering_method="NONE", |
| 64 | + relaxation_factor=relax, |
| 65 | + ) |
| 66 | + |
| 67 | + # create gwf model |
| 68 | + newtonoptions = "NEWTON UNDER_RELAXATION" |
| 69 | + gwf = flopy.mf6.ModflowGwf( |
| 70 | + sim, |
| 71 | + modelname=name, |
| 72 | + newtonoptions=newtonoptions, |
| 73 | + save_flows=True, |
| 74 | + ) |
| 75 | + |
| 76 | + dis = flopy.mf6.ModflowGwfdis( |
| 77 | + gwf, |
| 78 | + crs=crs, |
| 79 | + nlay=nlay, |
| 80 | + nrow=nrow, |
| 81 | + ncol=ncol, |
| 82 | + delr=delr, |
| 83 | + delc=delc, |
| 84 | + top=top, |
| 85 | + botm=botm, |
| 86 | + idomain=np.ones((nlay, nrow, ncol), dtype=int), |
| 87 | + ) |
| 88 | + |
| 89 | + # initial conditions |
| 90 | + ic = flopy.mf6.ModflowGwfic(gwf, strt=strt) |
| 91 | + |
| 92 | + # node property flow |
| 93 | + npf = flopy.mf6.ModflowGwfnpf(gwf, save_flows=False, icelltype=laytyp, k=hk) |
| 94 | + # storage |
| 95 | + sto = flopy.mf6.ModflowGwfsto( |
| 96 | + gwf, |
| 97 | + save_flows=False, |
| 98 | + iconvert=laytyp, |
| 99 | + ss=ss, |
| 100 | + sy=sy, |
| 101 | + steady_state={0: False}, |
| 102 | + transient={0: True}, |
| 103 | + ) |
| 104 | + |
| 105 | + # ghbg |
| 106 | + ghb_obs = {f"{name}.ghb.obs.csv": [("100_1_1", "GHB", (99, 0, 0))]} |
| 107 | + bhead = np.full(nlay * nrow * ncol, DNODATA, dtype=float) |
| 108 | + cond = np.full(nlay * nrow * ncol, DNODATA, dtype=float) |
| 109 | + bhead[nlay - 1] = 1.5 |
| 110 | + cond[nlay - 1] = 1.0 |
| 111 | + ghb = flopy.mf6.ModflowGwfghbg( |
| 112 | + gwf, |
| 113 | + print_input=True, |
| 114 | + print_flows=True, |
| 115 | + bhead=bhead, |
| 116 | + cond=cond, |
| 117 | + save_flows=False, |
| 118 | + ) |
| 119 | + |
| 120 | + ghb.obs.initialize( |
| 121 | + filename=f"{name}.ghb.obs", |
| 122 | + digits=20, |
| 123 | + print_input=True, |
| 124 | + continuous=ghb_obs, |
| 125 | + ) |
| 126 | + |
| 127 | + # note: for specifying lake number, use fortran indexing! |
| 128 | + uzf_obs = { |
| 129 | + f"{name}.uzf.obs.csv": [ |
| 130 | + ("wc 02", "water-content", 2, 0.5), |
| 131 | + ("wc 50", "water-content", 50, 0.5), |
| 132 | + ("wcbn 02", "water-content", "uzf 002", 0.5), |
| 133 | + ("wcbn 50", "water-content", "UZF 050", 0.5), |
| 134 | + ("rch 02", "uzf-gwrch", "uzf 002"), |
| 135 | + ("rch 50", "uzf-gwrch", "uzf 050"), |
| 136 | + ] |
| 137 | + } |
| 138 | + |
| 139 | + sd = 0.1 |
| 140 | + vks = hk |
| 141 | + thtr = 0.05 |
| 142 | + thti = thtr |
| 143 | + thts = sy |
| 144 | + eps = 4 |
| 145 | + uzf_pkdat = [[0, (0, 0, 0), 1, 1, sd, vks, thtr, thts, thti, eps, "uzf 001"]] + [ |
| 146 | + [k, (k, 0, 0), 0, k + 1, sd, vks, thtr, thts, thti, eps, f"uzf {k + 1:03d}"] |
| 147 | + for k in range(1, nlay - 1) |
| 148 | + ] |
| 149 | + uzf_pkdat[-1][3] = -1 |
| 150 | + infiltration = 2.01 |
| 151 | + uzf_spd = {0: [[0, infiltration, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]} |
| 152 | + uzf = flopy.mf6.ModflowGwfuzf( |
| 153 | + gwf, |
| 154 | + print_input=True, |
| 155 | + print_flows=True, |
| 156 | + save_flows=True, |
| 157 | + boundnames=True, |
| 158 | + ntrailwaves=15, |
| 159 | + nwavesets=40, |
| 160 | + nuzfcells=len(uzf_pkdat), |
| 161 | + packagedata=uzf_pkdat, |
| 162 | + perioddata=uzf_spd, |
| 163 | + budget_filerecord=f"{name}.uzf.bud", |
| 164 | + budgetcsv_filerecord=f"{name}.uzf.bud.csv", |
| 165 | + observations=uzf_obs, |
| 166 | + filename=f"{name}.uzf", |
| 167 | + ) |
| 168 | + |
| 169 | + # output control |
| 170 | + oc = flopy.mf6.ModflowGwfoc( |
| 171 | + gwf, |
| 172 | + budget_filerecord=f"{name}.bud", |
| 173 | + head_filerecord=f"{name}.hds", |
| 174 | + headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")], |
| 175 | + saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")], |
| 176 | + printrecord=[("HEAD", "LAST"), ("BUDGET", "ALL")], |
| 177 | + ) |
| 178 | + |
| 179 | + obs_lst = [] |
| 180 | + obs_lst.append(["obs1", "head", (0, 0, 0)]) |
| 181 | + obs_lst.append(["obs2", "head", (1, 0, 0)]) |
| 182 | + obs_dict = {f"{name}.obs.csv": obs_lst} |
| 183 | + obs = flopy.mf6.ModflowUtlobs(gwf, pname="head_obs", digits=20, continuous=obs_dict) |
| 184 | + |
| 185 | + return sim |
| 186 | + |
| 187 | + |
| 188 | +def add_netcdf_vars(dataset, nc_info, dimmap): |
| 189 | + def _data_shape(shape): |
| 190 | + dims_l = [] |
| 191 | + for d in shape: |
| 192 | + dims_l.append(dimmap[d]) |
| 193 | + |
| 194 | + return dims_l |
| 195 | + |
| 196 | + for v in nc_info: |
| 197 | + varname = nc_info[v]["varname"] |
| 198 | + data = np.full( |
| 199 | + _data_shape(nc_info[v]["nc_shape"]), |
| 200 | + nc_info[v]["attrs"]["_FillValue"], |
| 201 | + dtype=nc_info[v]["nc_type"], |
| 202 | + ) |
| 203 | + var_d = {varname: (nc_info[v]["nc_shape"], data)} |
| 204 | + dataset = dataset.assign(var_d) |
| 205 | + for a in nc_info[v]["attrs"]: |
| 206 | + dataset[varname].attrs[a] = nc_info[v]["attrs"][a] |
| 207 | + |
| 208 | + return dataset |
| 209 | + |
| 210 | + |
| 211 | +temp_dir = TemporaryDirectory() |
| 212 | +workspace = Path(temp_dir.name) |
| 213 | + |
| 214 | +# run the non-netcdf simulation |
| 215 | +sim = create_sim(ws=workspace) |
| 216 | +sim.write_simulation() |
| 217 | +success, buff = sim.run_simulation(silent=True, report=True) |
| 218 | +assert success, pformat(buff) |
| 219 | + |
| 220 | +# create directory for netcdf sim |
| 221 | +sim.set_sim_path(workspace / "netcdf") |
| 222 | +gwf = sim.get_model("uzf01") |
| 223 | +gwf.name_file.nc_filerecord = "uzf01.structured.nc" |
| 224 | +sim.write_simulation() |
| 225 | + |
| 226 | +# create the netcdf dataset |
| 227 | +ds = xr.Dataset() |
| 228 | + |
| 229 | +# get model netcdf info |
| 230 | +nc_info = gwf.netcdf_info() |
| 231 | + |
| 232 | +# update dataset with required attributes |
| 233 | +for a in nc_info["attrs"]: |
| 234 | + ds.attrs[a] = nc_info["attrs"][a] |
| 235 | + |
| 236 | +# set dimensional info |
| 237 | +dis = gwf.modelgrid |
| 238 | +xoff = dis.xoffset |
| 239 | +yoff = dis.yoffset |
| 240 | +x = xoff + dis.xycenters[0] |
| 241 | +y = yoff + dis.xycenters[1] |
| 242 | +z = [float(x) for x in range(1, dis.nlay + 1)] |
| 243 | +nstp = sum(gwf.modeltime.nstp) |
| 244 | +time = gwf.modeltime.tslen |
| 245 | +nlay = dis.nlay |
| 246 | +nrow = dis.nrow |
| 247 | +ncol = dis.ncol |
| 248 | +dimmap = {"time": nstp, "z": nlay, "y": nrow, "x": ncol} |
| 249 | + |
| 250 | +# create coordinate vars |
| 251 | +var_d = {"time": (["time"], time), "z": (["z"], z), "y": (["y"], y), "x": (["x"], x)} |
| 252 | +ds = ds.assign(var_d) |
| 253 | + |
| 254 | +# dis |
| 255 | +dis = gwf.get_package("dis") |
| 256 | +nc_info = dis.netcdf_info() |
| 257 | + |
| 258 | +# create dis dataset variables |
| 259 | +ds = add_netcdf_vars(ds, nc_info, dimmap) |
| 260 | + |
| 261 | +# update data |
| 262 | +ds["dis_delr"].values = dis.delr.get_data() |
| 263 | +ds["dis_delc"].values = dis.delc.get_data() |
| 264 | +ds["dis_top"].values = dis.top.get_data() |
| 265 | +ds["dis_botm"].values = dis.botm.get_data() |
| 266 | +ds["dis_idomain"].values = dis.idomain.get_data() |
| 267 | + |
| 268 | +# update dis to read from netcdf |
| 269 | +with open(workspace / "netcdf" / "uzf01.dis", "w") as f: |
| 270 | + f.write("BEGIN options\n") |
| 271 | + f.write(" crs EPSG:26916\n") |
| 272 | + f.write("END options\n\n") |
| 273 | + f.write("BEGIN dimensions\n") |
| 274 | + f.write(" NLAY 100\n") |
| 275 | + f.write(" NROW 1\n") |
| 276 | + f.write(" NCOL 1\n") |
| 277 | + f.write("END dimensions\n\n") |
| 278 | + f.write("BEGIN griddata\n") |
| 279 | + f.write(" delr NETCDF\n") |
| 280 | + f.write(" delc NETCDF\n") |
| 281 | + f.write(" top NETCDF\n") |
| 282 | + f.write(" botm NETCDF\n") |
| 283 | + f.write(" idomain NETCDF\n") |
| 284 | + f.write("END griddata\n") |
| 285 | + |
| 286 | +# npf |
| 287 | +npf = gwf.get_package("npf") |
| 288 | +nc_info = npf.netcdf_info() |
| 289 | + |
| 290 | +# create npf dataset variables |
| 291 | +ds = add_netcdf_vars(ds, nc_info, dimmap) |
| 292 | + |
| 293 | +# update data |
| 294 | +ds["npf_icelltype"].values = npf.icelltype.get_data() |
| 295 | +ds["npf_k"].values = npf.k.get_data() |
| 296 | + |
| 297 | +# update npf to read from netcdf |
| 298 | +with open(workspace / "netcdf" / "uzf01.npf", "w") as f: |
| 299 | + f.write("BEGIN options\n") |
| 300 | + f.write("END options\n\n") |
| 301 | + f.write("BEGIN griddata\n") |
| 302 | + f.write(" icelltype NETCDF\n") |
| 303 | + f.write(" k NETCDF\n") |
| 304 | + f.write("END griddata\n") |
| 305 | + |
| 306 | +# get ghbg package netcdf info |
| 307 | +ghbg = gwf.get_package("ghbg_0") |
| 308 | +nc_info = ghbg.netcdf_info() |
| 309 | + |
| 310 | +# create ghbg dataset variables |
| 311 | +ds = add_netcdf_vars(ds, nc_info, dimmap) |
| 312 | + |
| 313 | +# update bhead netcdf array from flopy perioddata |
| 314 | +for p in ghbg.bhead.get_data(): |
| 315 | + istp = sum(gwf.modeltime.nstp[0:p]) |
| 316 | + ds["ghbg_0_bhead"].values[istp] = ghbg.bhead.get_data()[p] |
| 317 | + |
| 318 | +# update cond netcdf array from flopy perioddata |
| 319 | +for p in ghbg.cond.get_data(): |
| 320 | + istp = sum(gwf.modeltime.nstp[0:p]) |
| 321 | + ds["ghbg_0_cond"].values[istp] = ghbg.cond.get_data()[p] |
| 322 | + |
| 323 | +# write the netcdf |
| 324 | +ds.to_netcdf( |
| 325 | + workspace / "netcdf/uzf01.structured.nc", format="NETCDF4", engine="netcdf4" |
| 326 | +) |
| 327 | + |
| 328 | +# update ghbg to read from netcdf |
| 329 | +with open(workspace / "netcdf/uzf01.ghbg", "w") as f: |
| 330 | + f.write("BEGIN options\n") |
| 331 | + f.write(" READARRAYGRID\n") |
| 332 | + f.write(" PRINT_INPUT\n") |
| 333 | + f.write(" PRINT_FLOWS\n") |
| 334 | + f.write(" OBS6 FILEIN uzf01.ghb.obs\n") |
| 335 | + f.write("END options\n\n") |
| 336 | + f.write("BEGIN period 1\n") |
| 337 | + f.write(" bhead NETCDF\n") |
| 338 | + f.write(" cond NETCDF\n") |
| 339 | + f.write("END period 1\n") |
| 340 | + |
| 341 | +# TODO need extended modflow 6 to run this simulation |
| 342 | +# run the netcdf sim |
| 343 | +# success, buff = sim.run_simulation(silent=True, report=True) |
| 344 | +# assert success, pformat(buff) |
0 commit comments