|
| 1 | +""" |
| 2 | +Tests ability to transform release point coordinates from |
| 3 | +global, local, or local offset coordinate representations |
| 4 | +to model coordinates, as well as to transform output data |
| 5 | +from model to global cooordinates. |
| 6 | +
|
| 7 | +The grid is a 10x10 square with a single layer, |
| 8 | +the same flow system shown on the FloPy readme. |
| 9 | +
|
| 10 | +Test cases are defined for each coordinate system option. |
| 11 | +These are: |
| 12 | +
|
| 13 | + - local_xy |
| 14 | + - local_xy_offset |
| 15 | + - dev_global_xy |
| 16 | +
|
| 17 | +""" |
| 18 | + |
| 19 | +import flopy |
| 20 | +import matplotlib.pyplot as plt |
| 21 | +import numpy as np |
| 22 | +import pandas as pd |
| 23 | +import pytest |
| 24 | +from flopy.utils.gridintersect import GridIntersect |
| 25 | +from framework import TestFramework |
| 26 | +from prt_test_utils import ( |
| 27 | + DEFAULT_EXIT_SOLVE_TOL, |
| 28 | + FlopyReadmeCase, |
| 29 | + get_model_name, |
| 30 | +) |
| 31 | +from shapely.geometry import Point |
| 32 | + |
| 33 | +simname = "prtcrd" |
| 34 | +cases = [f"{simname}mdl", f"{simname}lcl", f"{simname}ofs"] # todo global |
| 35 | +nodes = list(range(100)) |
| 36 | + |
| 37 | + |
| 38 | +def get_start_points(): |
| 39 | + return np.add( |
| 40 | + np.transpose(np.array(np.meshgrid(range(10), range(10))).reshape(2, -1)), |
| 41 | + 0.5, |
| 42 | + ) |
| 43 | + |
| 44 | + |
| 45 | +def get_prp(prt): |
| 46 | + gi = GridIntersect(prt.modelgrid, method="vertex", rtree=True) |
| 47 | + if "mdl" in prt.name: |
| 48 | + startpts = get_start_points() |
| 49 | + releasepts = [ |
| 50 | + (i, (0, *gi.intersect(Point(rpt))[0].cellids), *rpt, 0.5) |
| 51 | + for i, rpt in enumerate(startpts) |
| 52 | + ] |
| 53 | + elif "lcl" in prt.name: |
| 54 | + releasepts = [ |
| 55 | + (nn, tuple([*prt.modelgrid.get_lrc([nn])[0]]), 0.5, 0.5, 0.5) |
| 56 | + for nn in nodes |
| 57 | + ] |
| 58 | + elif "ofs" in prt.name: |
| 59 | + releasepts = [ |
| 60 | + (nn, tuple([*prt.modelgrid.get_lrc([nn])[0]]), 0.0, 0.0, 0.0) |
| 61 | + for nn in nodes |
| 62 | + ] |
| 63 | + elif "gbl" in prt.name: |
| 64 | + # todo global |
| 65 | + pass |
| 66 | + |
| 67 | + prp_track_file = f"{prt.name}_prt.prp.trk" |
| 68 | + prp_track_csv_file = f"{prt.name}_prt.prp.trk.csv" |
| 69 | + return flopy.mf6.ModflowPrtprp( |
| 70 | + prt, |
| 71 | + pname="prp1", |
| 72 | + filename=f"{prt.name}_1.prp", |
| 73 | + nreleasepts=len(releasepts), |
| 74 | + packagedata=releasepts, |
| 75 | + perioddata={0: ["FIRST"]}, |
| 76 | + track_filerecord=[prp_track_file], |
| 77 | + trackcsv_filerecord=[prp_track_csv_file], |
| 78 | + local_xy="lcl" in prt.name, |
| 79 | + local_xy_offset="ofs" in prt.name, |
| 80 | + exit_solve_tolerance=DEFAULT_EXIT_SOLVE_TOL, |
| 81 | + extend_tracking=True, |
| 82 | + ) |
| 83 | + |
| 84 | + |
| 85 | +def build_prt_sim(name, gwf_ws, prt_ws, mf6): |
| 86 | + # create simulation |
| 87 | + sim = flopy.mf6.MFSimulation( |
| 88 | + sim_name=name, |
| 89 | + exe_name=mf6, |
| 90 | + version="mf6", |
| 91 | + sim_ws=prt_ws, |
| 92 | + ) |
| 93 | + |
| 94 | + # create tdis package |
| 95 | + flopy.mf6.modflow.mftdis.ModflowTdis( |
| 96 | + sim, |
| 97 | + pname="tdis", |
| 98 | + time_units="DAYS", |
| 99 | + nper=FlopyReadmeCase.nper, |
| 100 | + perioddata=[ |
| 101 | + ( |
| 102 | + FlopyReadmeCase.perlen, |
| 103 | + FlopyReadmeCase.nstp, |
| 104 | + FlopyReadmeCase.tsmult, |
| 105 | + ) |
| 106 | + ], |
| 107 | + ) |
| 108 | + |
| 109 | + # create prt model |
| 110 | + prt_name = get_model_name(name, "prt") |
| 111 | + prt = flopy.mf6.ModflowPrt(sim, modelname=prt_name, save_flows=True) |
| 112 | + |
| 113 | + # create prt discretization |
| 114 | + flopy.mf6.modflow.mfgwfdis.ModflowGwfdis( |
| 115 | + prt, |
| 116 | + pname="dis", |
| 117 | + nlay=FlopyReadmeCase.nlay, |
| 118 | + nrow=FlopyReadmeCase.nrow, |
| 119 | + ncol=FlopyReadmeCase.ncol, |
| 120 | + ) |
| 121 | + |
| 122 | + # create mip package |
| 123 | + flopy.mf6.ModflowPrtmip(prt, pname="mip", porosity=FlopyReadmeCase.porosity) |
| 124 | + |
| 125 | + # create prp package |
| 126 | + prp = get_prp(prt) |
| 127 | + |
| 128 | + # create output control package |
| 129 | + prt_budget_file = f"{prt_name}.bud" |
| 130 | + prt_track_file = f"{prt_name}.trk" |
| 131 | + prt_track_csv_file = f"{prt_name}.trk.csv" |
| 132 | + flopy.mf6.ModflowPrtoc( |
| 133 | + prt, |
| 134 | + pname="oc", |
| 135 | + budget_filerecord=[prt_budget_file], |
| 136 | + track_filerecord=[prt_track_file], |
| 137 | + trackcsv_filerecord=[prt_track_csv_file], |
| 138 | + saverecord=[("BUDGET", "ALL")], |
| 139 | + ) |
| 140 | + |
| 141 | + # create the flow model interface |
| 142 | + gwf_name = get_model_name(name, "gwf") |
| 143 | + gwf_budget_file = gwf_ws / f"{gwf_name}.bud" |
| 144 | + gwf_head_file = gwf_ws / f"{gwf_name}.hds" |
| 145 | + flopy.mf6.ModflowPrtfmi( |
| 146 | + prt, |
| 147 | + packagedata=[ |
| 148 | + ("GWFHEAD", gwf_head_file), |
| 149 | + ("GWFBUDGET", gwf_budget_file), |
| 150 | + ], |
| 151 | + ) |
| 152 | + |
| 153 | + # add explicit model solution |
| 154 | + ems = flopy.mf6.ModflowEms( |
| 155 | + sim, |
| 156 | + pname="ems", |
| 157 | + filename=f"{prt_name}.ems", |
| 158 | + ) |
| 159 | + sim.register_solution_package(ems, [prt.name]) |
| 160 | + |
| 161 | + return sim |
| 162 | + |
| 163 | + |
| 164 | +def build_models(idx, test): |
| 165 | + gwf_ws = test.workspace / "gwf" |
| 166 | + prt_ws = test.workspace / "prt" |
| 167 | + gwf_sim = FlopyReadmeCase.get_gwf_sim(test.name, gwf_ws, test.targets["mf6"]) |
| 168 | + prt_sim = build_prt_sim(test.name, gwf_ws, prt_ws, test.targets["mf6"]) |
| 169 | + return gwf_sim, prt_sim |
| 170 | + |
| 171 | + |
| 172 | +def check_output(idx, test): |
| 173 | + name = test.name |
| 174 | + gwf_ws = test.workspace |
| 175 | + prt_ws = test.workspace / "prt" |
| 176 | + gwf_sim, prt_sim = test.sims[:2] |
| 177 | + gwf = gwf_sim.get_model() |
| 178 | + prt = prt_sim.get_model() |
| 179 | + hds = gwf.output.head().get_data() |
| 180 | + mg = gwf.modelgrid |
| 181 | + pathlines = pd.read_csv(prt_ws / f"{prt.name}.trk.csv") |
| 182 | + startpts = pathlines[pathlines.ireason == 0] |
| 183 | + endpts = pathlines[pathlines.ireason == 3] |
| 184 | + |
| 185 | + # check start points |
| 186 | + assert np.allclose( |
| 187 | + np.sort(startpts[["x", "y"]].to_numpy(), axis=0), |
| 188 | + np.sort(np.array(get_start_points()), axis=0), |
| 189 | + ) |
| 190 | + |
| 191 | + # setup plot |
| 192 | + plot_data = False |
| 193 | + if plot_data: |
| 194 | + fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 10)) |
| 195 | + for a in ax: |
| 196 | + a.set_aspect("equal") |
| 197 | + |
| 198 | + # plot pathline in map view |
| 199 | + pmv = flopy.plot.PlotMapView(modelgrid=mg, ax=ax[0]) |
| 200 | + pmv.plot_grid() |
| 201 | + pmv.plot_array(hds[0], alpha=0.1, cmap="jet") |
| 202 | + pmv.plot_pathline(pathlines) |
| 203 | + |
| 204 | + # view/save plot |
| 205 | + plt.show() |
| 206 | + plt.savefig(gwf_ws / f"test_{simname}.png") |
| 207 | + |
| 208 | + |
| 209 | +@pytest.mark.parametrize("idx, name", enumerate(cases)) |
| 210 | +def test_mf6model(idx, name, function_tmpdir, targets): |
| 211 | + test = TestFramework( |
| 212 | + name=name, |
| 213 | + workspace=function_tmpdir, |
| 214 | + build=lambda t: build_models(idx, t), |
| 215 | + check=lambda t: check_output(idx, t), |
| 216 | + targets=targets, |
| 217 | + compare=None, |
| 218 | + ) |
| 219 | + test.run() |
0 commit comments