1+ # ---
2+ # jupyter:
3+ # jupytext:
4+ # cell_metadata_filter: -all
5+ # formats: ipynb,py:light
6+ # text_representation:
7+ # extension: .py
8+ # format_name: light
9+ # format_version: '1.5'
10+ # jupytext_version: 1.17.2
11+ # kernelspec:
12+ # display_name: Python 3
13+ # language: python
14+ # name: python3
15+ # metadata:
16+ # section: mf6
17+ # ---
18+
19+ # # MODFLOW 6: Generate MODFLOW 6 NetCDF input from existing FloPy sim
20+ #
21+ # ## NetCDF tutorial 1: MODFLOW 6 structured input file
22+ #
23+ # This tutorial demonstrates how to generate a MODFLOW 6 NetCDF file from
24+ # an existing FloPy simulation. In the tutorial, candidate array data is
25+ # added to an xarray dataset and annotated so that the generated NetCDF
26+ # file can be read by MODFLOW 6 as model input.
27+ #
28+ # This tutorial generates a structured NetCDF variant - for more information
29+ # on supported MODFLOW 6 NetCDF formats see:
30+ # [MODFLOW NetCDF Format](https://github.com/MODFLOW-ORG/modflow6/wiki/MODFLOW-NetCDF-Format).
31+ #
32+ # Note that NetCDF is only supported by the Extended version of MODFLOW 6.
33+ # A nightly windows build of Extended MODFLOW 6 is available from
34+ # [nightly build](https://github.com/MODFLOW-ORG/modflow6-nightly-build).
35+
36+ # package import
137import sys
238from pathlib import Path
339from pprint import pformat , pprint
1147print (sys .version )
1248print (f"flopy version: { flopy .__version__ } " )
1349
50+ # ## Define DNODATA constant
51+ #
52+ # DNODATA is an important constant for MODFLOW 6 timeseries grid input
53+ # data. It signifies that the cell has no data defined for the time step
54+ # in question. These cell values are discared and have no impact on the
55+ # simulation.
56+
57+ # DNODATA constant
1458DNODATA = 3.0e30
1559
60+ # ## Define ASCII input baseline simulation
61+ #
62+ # For the purposes of this tutorial, the specifics of this simulation
63+ # other than it is a candidate for NetCDF input are not a focus. It
64+ # is a NetCDF input candidate because it defines a candidate model type
65+ # (GWF6) with packages that support NetCDF input parameters.
66+ #
67+ # A NetCDF dataset will be created from array data in the DIS, NPF and
68+ # GHBG packages. Data will be copied from the package objects into dataset
69+ # arrays.
70+
1671
17- # A FloPy simulation ASCII sim that will be updated
18- # use netcdf inputs
72+ # A FloPy ASCII base simulation that will be updated use netcdf inputs
1973def create_sim (ws ):
2074 name = "uzf01"
2175 perlen = [500.0 ]
@@ -187,8 +241,16 @@ def create_sim(ws):
187241 return sim
188242
189243
190- # A subroutine that can update an xarray dataset
191- # with package netcdf information stored in a dict
244+ # ## Create helper function to update dataset
245+ #
246+ # This function updates an Xarray dataset to add variables described
247+ # in a FloPy provided dictionary.
248+ #
249+ # A dimension map variable relates FloPy and NetCDF dimensions names.
250+
251+
252+ # A subroutine that can update an xarray dataset with package
253+ # netcdf information stored in a dict
192254def add_netcdf_vars (dataset , nc_info , dimmap ):
193255 def _data_shape (shape ):
194256 dims_l = []
@@ -223,14 +285,25 @@ def _data_shape(shape):
223285assert success , pformat (buff )
224286
225287# create directory for netcdf sim
288+ # set model name file nc_filerecord attribute to export name
226289sim .set_sim_path (workspace / "netcdf" )
227290gwf = sim .get_model ("uzf01" )
228291gwf .name_file .nc_filerecord = "uzf01.structured.nc"
229292sim .write_simulation ()
230293
231- # create the netcdf dataset
294+ # create the dataset
232295ds = xr .Dataset ()
233296
297+ # ## Access model NetCDF attributes
298+ #
299+ # Access model scoped NetCDF details by storing the dictionary
300+ # returned from netcdf_info(). In particular, we need to set dataset
301+ # scoped attributes that are stored in the model netcdf info dict.
302+ #
303+ # First, retrieve and store the netcdf info dictionary and display
304+ # its contents. Then, in the following step, update the dataset with
305+ # the model scoped attributes defined in the dictionary.
306+
234307# get model netcdf info
235308nc_info = gwf .netcdf_info ()
236309pprint (nc_info )
@@ -239,7 +312,7 @@ def _data_shape(shape):
239312for a in nc_info ["attrs" ]:
240313 ds .attrs [a ] = nc_info ["attrs" ][a ]
241314
242- # set dimensional info
315+ # define dimensional info
243316dis = gwf .modelgrid
244317xoff = dis .xoffset
245318yoff = dis .yoffset
@@ -253,26 +326,53 @@ def _data_shape(shape):
253326ncol = dis .ncol
254327dimmap = {"time" : nstp , "z" : nlay , "y" : nrow , "x" : ncol }
255328
256- # create coordinate vars
329+ # create dataset coordinate vars
257330var_d = {"time" : (["time" ], time ), "z" : (["z" ], z ), "y" : (["y" ], y ), "x" : (["x" ], x )}
258331ds = ds .assign (var_d )
259332
260- # dis
333+ # ## Access package NetCDF attributes
334+ #
335+ # Access package scoped NetCDF details by storing the dictionary returned
336+ # from netcdf_info(). We need to set package variable attributes that are
337+ # stored in the package netcdf info dict, but we also need other information
338+ # that is relevant to creating the variables themselves.
339+ #
340+ # The contents of the info dictionary are shown and then, in the following
341+ # step, the dictionary and the dataset are passed to a helper routine that
342+ # create the intended array variables.
343+
344+ # get dis package netcdf info
261345dis = gwf .get_package ("dis" )
262346nc_info = dis .netcdf_info ()
263347pprint (nc_info )
264348
265349# create dis dataset variables
266350ds = add_netcdf_vars (ds , nc_info , dimmap )
267351
268- # update data
352+ # ## Update array data
353+ #
354+ # We have created dataset array variables for the package but they do not yet
355+ # define the expected input data for MODFLOW 6. We will take advantage of the
356+ # existing simulation objects and update the dataset.
357+
358+ # update dataset from dis arrays
269359ds ["dis_delr" ].values = dis .delr .get_data ()
270360ds ["dis_delc" ].values = dis .delc .get_data ()
271361ds ["dis_top" ].values = dis .top .get_data ()
272362ds ["dis_botm" ].values = dis .botm .get_data ()
273363ds ["dis_idomain" ].values = dis .idomain .get_data ()
274364
275- # update dis to read from netcdf
365+ # ## Update MODFLOW 6 package input file
366+ #
367+ # MODFLOW 6 input data for the package is now in the dataset. Once the NetCDF
368+ # file is generated, we need to configure MODFLOW 6 so that it looks to that
369+ # file for the package array input. The ASCII will no longer defined the arrays-
370+ # instead the array names will be followed by the NETCDF keyword.
371+ #
372+ # We will simply overwrite the entire MODFLOW 6 DIS package input file with the
373+ # following code block.
374+
375+ # rewrite mf6 dis input to read from netcdf
276376with open (workspace / "netcdf" / "uzf01.dis" , "w" ) as f :
277377 f .write ("BEGIN options\n " )
278378 f .write (" crs EPSG:26916\n " )
@@ -290,19 +390,23 @@ def _data_shape(shape):
290390 f .write (" idomain NETCDF\n " )
291391 f .write ("END griddata\n " )
292392
293- # npf
393+ # ## Update MODFLOW 6 package input file
394+ #
395+ # Follow the same process as above for the NPF package.
396+
397+ # get npf package netcdf info
294398npf = gwf .get_package ("npf" )
295399nc_info = npf .netcdf_info ()
296400pprint (nc_info )
297401
298402# create npf dataset variables
299403ds = add_netcdf_vars (ds , nc_info , dimmap )
300404
301- # update data
405+ # update dataset from npf arrays
302406ds ["npf_icelltype" ].values = npf .icelltype .get_data ()
303407ds ["npf_k" ].values = npf .k .get_data ()
304408
305- # update npf to read from netcdf
409+ # rewrite mf6 npf input to read from netcdf
306410with open (workspace / "netcdf" / "uzf01.npf" , "w" ) as f :
307411 f .write ("BEGIN options\n " )
308412 f .write ("END options\n \n " )
@@ -311,6 +415,14 @@ def _data_shape(shape):
311415 f .write (" k NETCDF\n " )
312416 f .write ("END griddata\n " )
313417
418+ # ## Update MODFLOW 6 package input file
419+ #
420+ # Follow the same process as above for the GHBG package. The difference is
421+ # that this is PERIOD input and therefore stored as timeseries data in the
422+ # NetCDF file. As NETCDF timeseries and defined in terms of total number of
423+ # simulation steps, care must be taken in the translation of FloPy period
424+ # data to the timeseries.
425+
314426# get ghbg package netcdf info
315427ghbg = gwf .get_package ("ghbg_0" )
316428nc_info = ghbg .netcdf_info ()
@@ -320,24 +432,26 @@ def _data_shape(shape):
320432ds = add_netcdf_vars (ds , nc_info , dimmap )
321433
322434# update bhead netcdf array from flopy perioddata
435+ # timeseries step index is first of stress period
323436for p in ghbg .bhead .get_data ():
324437 istp = sum (gwf .modeltime .nstp [0 :p ])
325438 ds ["ghbg_0_bhead" ].values [istp ] = ghbg .bhead .get_data ()[p ]
326439
327440# update cond netcdf array from flopy perioddata
441+ # timeseries step index is first of stress period
328442for p in ghbg .cond .get_data ():
329443 istp = sum (gwf .modeltime .nstp [0 :p ])
330444 ds ["ghbg_0_cond" ].values [istp ] = ghbg .cond .get_data ()[p ]
331445
332446# show the dataset
333447print (ds )
334448
335- # write the netcdf
449+ # write dataset to netcdf
336450ds .to_netcdf (
337451 workspace / "netcdf/uzf01.structured.nc" , format = "NETCDF4" , engine = "netcdf4"
338452)
339453
340- # update ghbg to read from netcdf
454+ # rewrite mf6 ghbg input to read from netcdf
341455with open (workspace / "netcdf/uzf01.ghbg" , "w" ) as f :
342456 f .write ("BEGIN options\n " )
343457 f .write (" READARRAYGRID\n " )
0 commit comments