Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
netcdf util uses data type specific interface
Browse files Browse the repository at this point in the history
mjreno authored and mjreno committed Jan 15, 2025
1 parent 46dc0ed commit 8c59095
Showing 5 changed files with 106 additions and 113 deletions.
4 changes: 2 additions & 2 deletions flopy/mf6/data/mfdataarray.py
Original file line number Diff line number Diff line change
@@ -1602,10 +1602,10 @@ def _set_storage_netcdf(self, nc_dataset, create=False):
input_tag = f"{m}/{p}/{n}".lower()

if create:
# cache data and update file before resetting storage
# add array to netcdf dataset
nc_varname = f"{p}_{n}".lower()
data = self._get_data()
nc_dataset.create_var(nc_varname, input_tag, 0, data, self.structure.shape)
nc_dataset.create_array(nc_varname, input_tag, 0, data, self.structure.shape)

storage._set_storage_netcdf(
nc_dataset, input_tag, self.structure.layered, storage.layer_storage.get_total_size() - 1
2 changes: 1 addition & 1 deletion flopy/mf6/data/mffileaccess.py
Original file line number Diff line number Diff line change
@@ -473,7 +473,7 @@ def load_netcdf_array(
nc_tag,
layer,
):
return nc_dataset.data(nc_tag, layer + 1)
return nc_dataset.array(nc_tag, layer + 1)

def get_data_string(self, data, data_type, data_indent=""):
layer_data_string = [str(data_indent)]
2 changes: 1 addition & 1 deletion flopy/mf6/data/mfstructure.py
Original file line number Diff line number Diff line change
@@ -1425,7 +1425,7 @@ def __init__(self, data_item, model_data, package_type, dfn_list):
or "nodes" in data_item.shape
or len(data_item.layer_dims) > 1
)
# TODO: only gwf, gwt, gwe models
# TODO: only gwf, gwt, gwe models NETCDF-DEV
self.netcdf = (
("ncol" in data_item.shape
or "nrow" in data_item.shape
15 changes: 11 additions & 4 deletions flopy/mf6/mfmodel.py
Original file line number Diff line number Diff line change
@@ -940,19 +940,26 @@ def load_base(
dis_str = {
"dis6": "structured",
"disv6": "vertex",
"disu6": "unstructured",
#"disu6": "unstructured",
}
dis_type = None
for t in instance.name_file.packages.get_data():
if t[0].lower().startswith("dis"):
dis_type = t[0].lower()
break
if dis_type:
if dis_type and dis_type in dis_str:
nc_fpth = os.path.join(instance.model_ws, nc_filerecord[0][0])
instance._nc_dataset = open_netcdf_dataset(nc_fpth, dis_type=dis_str[dis_type])
else:
pass
# TODO: set error "invalid dis for netcdf input"
message = (
"Invalid discretization type "
f"provided for model {modelname} "
"NetCDF input"
)
raise MFDataException(
model=modelname,
message=message,
)

# order packages
vnum = mfstructure.MFStructure().get_version_string()
196 changes: 91 additions & 105 deletions flopy/utils/model_netcdf.py
Original file line number Diff line number Diff line change
@@ -46,11 +46,8 @@ def open(self, nc_fpth: str) -> None:
self._dpth = fpth.parent
self._name = fpth.name

try:
self._dataset = xr.open_dataset(fpth, engine="netcdf4")
self._set_mapping()
except Exception as e:
print(f"Exception: {e}")
self._dataset = xr.open_dataset(fpth, engine="netcdf4")
self._set_mapping()

def create(
self, model_type: str, model_name: str, nc_type: str, fname: str, dis: Grid
@@ -60,24 +57,20 @@ def create(
self._name = fname
self._tags = {}

assert self._type == "mesh2d" or self._type == "structured"
if self._type != "mesh2d" and self._type != "structured":
raise Exception('Supported NetCDF file types are "mesh2d" and "structured"')
if isinstance(dis, VertexGrid) and self._type != "mesh2d":
# TODO error
pass

try:
self._dataset = xr.Dataset()
self._set_global_attrs(model_type, model_name)
self._set_grid(dis)
except Exception as e:
print(f"Exception: {e}")
raise Exception("VertexGrid object must use mesh2d netcdf file type")

self._dataset = xr.Dataset()
self._set_global_attrs(model_type, model_name)
self._set_grid(dis)
# print(self._dataset.info())

def create_var(
def create_array(
self, varname: str, tag: str, layer: int, data: np.typing.ArrayLike, shape: list
):
raise NotImplementedError("create_var not implemented in base class")
raise NotImplementedError("create_array not implemented in base class")

def write(self, path: str) -> None:
nc_fpath = Path(path) / self._name
@@ -90,14 +83,13 @@ def layered(self) -> bool:

return res

def data(self, path, layer=None):
raise NotImplementedError("data not implemented in base class")
def array(self, path, layer=None):
raise NotImplementedError("array not implemented in base class")

def _set_mapping(self):
var_d = {}
if ("modflow6_grid" and "modflow6_model") not in self._dataset.attrs:
# TODO error
pass
raise Exception("Invalid MODFLOW 6 netcdf dataset")
else:
self._modelname = (
self._dataset.attrs["modflow6_model"].split(":")[0].lower()
@@ -133,8 +125,7 @@ def _set_global_attrs(self, model_type, model_name):
dep_var = "temperature"
model = "Groundwater Energy (GWE)"
else:
# TODO error?
pass
raise Exception("NetCDF supported for GWF, GWT and GWE models")

if self._type == "structured":
grid = self._type.upper()
@@ -155,48 +146,43 @@ def _set_global_attrs(self, model_type, model_name):
def _set_grid(self, dis):
raise NotImplementedError("_set_grid not implemented in base class")

def _create_var(
def _create_array(
self, varname: str, tag: str, data: np.typing.ArrayLike, nc_shape: list
):
try:
layer = 0
var_d = {varname: (nc_shape, data)}
layer = 0
var_d = {varname: (nc_shape, data)}
self._dataset = self._dataset.assign(var_d)
self._dataset[varname].attrs["modflow6_input"] = tag
if tag not in self._tags:
self._tags[tag] = {}
if layer in self._tags[tag]:
raise Exception(f"Array variable tag already exists: {tag}")
self._tags[tag][layer] = varname

def _create_layered_array(
self, varname: str, tag: str, data: np.typing.ArrayLike, nc_shape: list
):
for layer in range(data.shape[0]):
mf6_layer = layer + 1
layer_vname = f"{varname}_l{mf6_layer}"
var_d = {layer_vname: (nc_shape, data[layer].flatten())}
self._dataset = self._dataset.assign(var_d)
self._dataset[varname].attrs["modflow6_input"] = tag
self._dataset[layer_vname].attrs["modflow6_input"] = tag
self._dataset[layer_vname].attrs["modflow6_layer"] = layer + 1
if tag not in self._tags:
self._tags[tag] = {}
if layer in self._tags[tag]:
# TODO error?
pass
self._tags[tag][layer] = varname
except Exception as e:
print(f"Exception: {e}")

def _create_layered_var(
self, varname: str, tag: str, data: np.typing.ArrayLike, nc_shape: list
):
try:
for layer in range(data.shape[0]):
layer_vname = f"{varname}_l{layer + 1}"
var_d = {layer_vname: (nc_shape, data[layer].flatten())}
self._dataset = self._dataset.assign(var_d)
self._dataset[layer_vname].attrs["modflow6_input"] = tag
self._dataset[layer_vname].attrs["modflow6_layer"] = layer + 1
if tag not in self._tags:
self._tags[tag] = {}
if layer in self._tags[tag]:
# TODO error?
pass
self._tags[tag][layer + 1] = layer_vname
except Exception as e:
print(f"Exception: {e}")
if mf6_layer in self._tags[tag]:
raise Exception(
f"Array variable tag already exists: {tag}, layer={layer}"
)
self._tags[tag][mf6_layer] = layer_vname


class DisNetCDFStructured(ModelNetCDFDataset):
def __init__(self):
super().__init__()

def create_var(
def create_array(
self, varname: str, tag: str, layer: int, data: np.typing.ArrayLike, shape: list
):
nc_shape = None
@@ -209,39 +195,39 @@ def create_var(
nc_shape = ["y"]
elif shape[0].lower() == "ncol":
nc_shape = ["x"]
else:
# TODO error?
pass

self._create_var(varname, tag, data, nc_shape)
self._create_array(varname, tag, data, nc_shape)

def data(self, path, layer=None):
def array(self, path, layer=None):
if len(self._dataset[self._tags[path][0]].data.shape) == 3:
return self._dataset[self._tags[path][0]].data[layer - 1]
else:
return self._dataset[self._tags[path][0]].data

def _set_grid(self, dis):
x = dis.xcellcenters[0]
y = dis.ycellcenters[:, 0]
z = list(range(1, dis.nlay + 1))
# lenunits = {0: "undefined", 1: "feet", 2: "meters", 3: "centimeters"}
lenunits = {0: "m", 1: "feet", 2: "m", 3: "cm"}

x_bnds = []
for idx, x in enumerate(dis.xyedges[0]):
for idx, val in enumerate(dis.xyedges[0]):
if idx + 1 < len(dis.xyedges[0]):
bnd = []
bnd.append(dis.xyedges[0][idx])
bnd.append(dis.xyedges[0][idx + 1])
x_bnds.append(bnd)

y_bnds = []
for idx, y in enumerate(dis.xyedges[1]):
for idx, val in enumerate(dis.xyedges[1]):
if idx + 1 < len(dis.xyedges[1]):
bnd = []
bnd.append(dis.xyedges[1][idx + 1])
bnd.append(dis.xyedges[1][idx])
y_bnds.append(bnd)

x = dis.xcellcenters[0]
y = dis.ycellcenters[:, 0]
z = list(range(1, dis.nlay + 1))

# create coordinate vars
var_d = {"z": (["z"], z), "y": (["y"], y), "x": (["x"], x)}
self._dataset = self._dataset.assign(var_d)
@@ -253,29 +239,25 @@ def _set_grid(self, dis):
# set coordinate variable attributes
self._dataset["z"].attrs["units"] = "layer"
self._dataset["z"].attrs["long_name"] = "layer number"
self._dataset["y"].attrs["units"] = "m" # TODO in dis?
self._dataset["y"].attrs["units"] = lenunits[dis.lenuni]
self._dataset["y"].attrs["axis"] = "Y"
self._dataset["y"].attrs["standard_name"] = "projection_y_coordinate"
self._dataset["y"].attrs["long_name"] = "Northing"
self._dataset["y"].attrs["grid_mapping"] = "projection" # TODO
# self._dataset["y"].attrs["grid_mapping"] = "projection" # TODO
self._dataset["y"].attrs["bounds"] = "y_bnds"
self._dataset["x"].attrs["units"] = "m" # TODO in dis?
self._dataset["x"].attrs["units"] = lenunits[dis.lenuni]
self._dataset["x"].attrs["axis"] = "X"
self._dataset["x"].attrs["standard_name"] = "projection_x_coordinate"
self._dataset["x"].attrs["long_name"] = "Easting"
self._dataset["x"].attrs["grid_mapping"] = "projection" # TODO
# self._dataset["x"].attrs["grid_mapping"] = "projection" # TODO
self._dataset["x"].attrs["bounds"] = "x_bnds"


# TODO: base class ModelNetCDFMesh2d(ModelNetCDFDataset) ?
# for common routines e.g. data()


class DisNetCDFMesh2d(ModelNetCDFDataset):
def __init__(self):
super().__init__()

def create_var(
def create_array(
self, varname: str, tag: str, layer: int, data: np.typing.ArrayLike, shape: list
):
nc_shape = None
@@ -288,11 +270,11 @@ def create_var(
nc_shape = ["nmesh_face"]

if len(data.shape) == 3:
self._create_layered_var(varname, tag, data, nc_shape)
self._create_layered_array(varname, tag, data, nc_shape)
else:
self._create_var(varname, tag, data.flatten(), nc_shape)
self._create_array(varname, tag, data.flatten(), nc_shape)

def data(self, path, layer=None):
def array(self, path, layer=None):
if not layer:
layer = 0
if path in self._tags:
@@ -305,7 +287,7 @@ def _set_grid(self, dis):
print(dir(dis))
print(dis.ncpl)
print(dis.nvert)
print(dis.get_cell_vertices())
# print(dis.get_cell_vertices())
# nmesh_node = dis.nvert
# nmesh_face = dis.ncpl
# max_nmesh_face_nodes = 4 ; # assume 4 for dis?
@@ -316,17 +298,17 @@ class DisvNetCDFMesh2d(ModelNetCDFDataset):
def __init__(self):
super().__init__()

def create_var(
def create_array(
self, varname: str, tag: str, layer: int, data: np.typing.ArrayLike, shape: list
):
nc_shape = ["nmesh_face"]

if len(data.shape) == 2:
self._create_layered_var(varname, tag, data, nc_shape)
self._create_layered_array(varname, tag, data, nc_shape)
else:
self._create_var(varname, tag, data.flatten(), nc_shape)
self._create_array(varname, tag, data.flatten(), nc_shape)

def data(self, path, layer=None):
def array(self, path, layer=None):
if not layer:
layer = 0
if path in self._tags:
@@ -343,35 +325,37 @@ def open_netcdf_dataset(nc_fpth: str, dis_type: str) -> ModelNetCDFDataset:
# dis_type corresponds to a flopy.discretization derived object type
nc_dataset = None
dis_str = dis_type.lower()
assert dis_str == "structured" or dis_str == "vertex"
if dis_str != "vertex" and dis_str != "structured":
raise Exception(
"Supported NetCDF discretication types "
'are "vertex" (DISV) and "structured" '
"(DIS)"
)

fpth = Path(nc_fpth).resolve()
try:
dataset = xr.open_dataset(fpth, engine="netcdf4")
if ("modflow6_grid" and "modflow6_model") not in dataset.attrs:
# TODO error
pass
else:
modelname = dataset.attrs["modflow6_model"].split(":")[0].lower()
gridtype = dataset.attrs["modflow6_grid"].lower()
if dis_str == "vertex":
if gridtype == "layered mesh":
nc_dataset = DisvNetCDFMesh2d()
elif dis_str == "structured":
if gridtype == "layered mesh":
nc_dataset = DisNetCDFMesh2d()
elif gridtype == "structured":
nc_dataset = DisNetCDFStructured()

dataset.close()
except Exception as e:
print(f"Exception: {e}")
dataset = xr.open_dataset(fpth, engine="netcdf4")
if ("modflow6_grid" and "modflow6_model") not in dataset.attrs:
modelname = None
gridtype = None
else:
modelname = dataset.attrs["modflow6_model"].split(":")[0].lower()
gridtype = dataset.attrs["modflow6_grid"].lower()
if dis_str == "vertex":
if gridtype == "layered mesh":
nc_dataset = DisvNetCDFMesh2d()
elif dis_str == "structured":
if gridtype == "layered mesh":
nc_dataset = DisNetCDFMesh2d()
elif gridtype == "structured":
nc_dataset = DisNetCDFStructured()

dataset.close()

if nc_dataset:
fpth = Path(nc_fpth).resolve()
nc_dataset.open(fpth)
else:
raise TypeError(
raise Exception(
f"Unable to load netcdf dataset for file grid "
f'type "{gridtype}" and discretization grid '
f'type "{dis_str}"'
@@ -380,7 +364,9 @@ def open_netcdf_dataset(nc_fpth: str, dis_type: str) -> ModelNetCDFDataset:
return nc_dataset


def create_netcdf_dataset(model_type, name, nc_type, nc_fname, dis):
def create_netcdf_dataset(
model_type, name, nc_type, nc_fname, dis
) -> ModelNetCDFDataset:
assert (
model_type.lower() == "gwf6"
or model_type.lower() == "gwt6"
@@ -399,7 +385,7 @@ def create_netcdf_dataset(model_type, name, nc_type, nc_fname, dis):
if nc_dataset:
nc_dataset.create(model_type, name, nc_type, nc_fname, dis)
else:
raise TypeError(
raise Exception(
f"Unable to generate netcdf dataset for file type "
f'"{nc_type.lower()}" and discretization grid type '
f'"{dis.grid_type}"'

0 comments on commit 8c59095

Please sign in to comment.