From 74be137ef05cde27af177cbab66b3e65627b2013 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Wed, 21 Aug 2024 16:06:22 -0700 Subject: [PATCH 01/17] test unpin numpy --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dc72d8a..73575ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ classifiers = [ ] requires-python = ">=3.9" dependencies = [ - "numpy<2.0.0", + "numpy", "pandas", "xmipy", ] From cf9233dd784d47a21240f1aa233e2cfd1e885dd1 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Tue, 8 Apr 2025 16:53:20 -0700 Subject: [PATCH 02/17] feat(advanced support): internal refactoring for advanced package support * refactored and added support to ListInput for advanced package blocks * extended AdvancedPackage base class support * moved package variable dictionary and package type lookup to datamodel.py --- modflowapi/extensions/advpaks.py | 145 +++++++++++ modflowapi/extensions/apimodel.py | 54 +--- modflowapi/extensions/apisimulation.py | 2 +- modflowapi/extensions/data.py | 332 +++++++++++++++--------- modflowapi/extensions/datamodel.py | 295 +++++++++++++++++++++ modflowapi/extensions/pakbase.py | 345 +++++++++++-------------- 6 files changed, 808 insertions(+), 365 deletions(-) create mode 100644 modflowapi/extensions/advpaks.py create mode 100644 modflowapi/extensions/datamodel.py diff --git a/modflowapi/extensions/advpaks.py b/modflowapi/extensions/advpaks.py new file mode 100644 index 0000000..8d15ce5 --- /dev/null +++ b/modflowapi/extensions/advpaks.py @@ -0,0 +1,145 @@ +from .pakbase import AdvancedPackage +from .data import ListInput +import numpy as np + + +class SfrPakage(AdvancedPackage): + """ + Container for SFR and SFR like packages + + Parameters + ---------- + model : ApiModel + modflowapi model object + pkg_type : str + package type. Ex. "SFR" + pkg_name : str + package name (in the mf6 variables) + sim_package : bool + boolean flag for simulation level packages. Ex. TDIS, IMS + """ + def __init__(self, model, pkg_type, pkg_name, sim_package=False): + super().__init__(model, pkg_type, pkg_name, sim_package) + + self._diversion_var_arrs = [] + self._set_advanced_variable_addrs("diversions", "_diversion_var_addrs") + self._diversion_vars = ListInput(self, self._diversion_var_arrs, spd=False) + + @property + def diversions(self): + return self._diversion_vars + + @diversions.setter + def diversions(self, recarray): + """ + Setter object to update the diversions data + + """ + if isinstance(recarray, np.recarray): + self._package_vars.values = recarray + elif isinstance(recarray, ListInput): + self._package_vars.values = recarray.values + elif recarray is None: + self._package_vars.values = recarray + else: + raise TypeError( + f"{type(recarray)} is not a supported stress_period_data type" + ) + + +class LakPackage: + """ + Container for LAK and LAK like packages + + Parameters + ---------- + model : ApiModel + modflowapi model object + pkg_type : str + package type. Ex. "LAK" + pkg_name : str + package name (in the mf6 variables) + sim_package : bool + boolean flag for simulation level packages. Ex. TDIS, IMS + """ + + def __init__(self, model, pkg_type, pkg_name, sim_package=False): + super().__init__(model, pkg_type, pkg_name, sim_package) + + +class MawPackage: + """ + Container for MAW and MAW like packages + + Parameters + ---------- + model : ApiModel + modflowapi model object + pkg_type : str + package type. Ex. "MAW" + pkg_name : str + package name (in the mf6 variables) + sim_package : bool + boolean flag for simulation level packages. Ex. TDIS, IMS + """ + + def __init__(self, model, pkg_type, pkg_name, sim_package=False): + super().__init__(model, pkg_type, pkg_name, sim_package) + + +class UzfPackage: + """ + Container for UZF and UZF like packages + + Parameters + ---------- + model : ApiModel + modflowapi model object + pkg_type : str + package type. Ex. "UZF" + pkg_name : str + package name (in the mf6 variables) + sim_package : bool + boolean flag for simulation level packages. Ex. TDIS, IMS + """ + + def __init__(self, model, pkg_type, pkg_name, sim_package=False): + super().__init__(model, pkg_type, pkg_name, sim_package) + + +class CsubPackage: + """ + Container for CSUB packages + + Parameters + ---------- + model : ApiModel + modflowapi model object + pkg_type : str + package type. Ex. "CSUB" + pkg_name : str + package name (in the mf6 variables) + sim_package : bool + boolean flag for simulation level packages. Ex. TDIS, IMS + """ + def __init__(self, ): + self.x = None + + +class MvrPackage: + """ + Container for MVR packages + + Parameters + ---------- + model : ApiModel + modflowapi model object + pkg_type : str + package type. Ex. "MVR" + pkg_name : str + package name (in the mf6 variables) + sim_package : bool + boolean flag for simulation level packages. Ex. TDIS, IMS + """ + def __init__(self, ): + self.x = None \ No newline at end of file diff --git a/modflowapi/extensions/apimodel.py b/modflowapi/extensions/apimodel.py index 4504821..664578e 100644 --- a/modflowapi/extensions/apimodel.py +++ b/modflowapi/extensions/apimodel.py @@ -7,13 +7,7 @@ package_factory, ) -gridshape = { - "dis": ["nlay", "nrow", "ncol"], - "disu": [ - "nlay", - "ncpl", - ], -} +from .datamodel import gridshape, get_package_type class ApiMbase: @@ -26,16 +20,16 @@ class ApiMbase: initialized ModflowApi object name : str modflow model name. ex. "GWF_1", "GWF-GWF_1" - pkg_types : dict - dictionary of package types and ApiPackage class types + pkg_types : None, dict + optional dictionary of package types and ApiPackage class types """ - def __init__(self, mf6, name, pkg_types): + def __init__(self, mf6, name, pkg_types=None): self.mf6 = mf6 self.name = name self._pkg_names = None self._pak_type = None - self.pkg_types = pkg_types + self._pkg_types = pkg_types self.package_dict = {} self._set_package_names() self._create_package_list() @@ -84,10 +78,13 @@ def _create_package_list(self): """ for ix, pkg_name in enumerate(self._pkg_names): pkg_type = self._pak_type[ix].lower() - if pkg_type in self.pkg_types: - basepackage = self.pkg_types[pkg_type] + if self._pkg_types is None: + basepackage = get_package_type(pkg_type) else: - basepackage = AdvancedPackage + if pkg_type in self._pkg_types: + basepackage = self._pkg_types[pkg_type] + else: + basepackage = AdvancedPackage package = package_factory(pkg_type, basepackage) adj_pkg_name = "".join(pkg_type.split("-")) @@ -152,33 +149,6 @@ def __init__(self, mf6, name): f"Unrecognized discretization type {grid_type}" ) - pkg_types = { - "dis": ArrayPackage, - "chd": ListPackage, - "drn": ListPackage, - "evt": ListPackage, - "ghb": ListPackage, - "ic": ArrayPackage, - "npf": ArrayPackage, - "rch": ListPackage, - "riv": ListPackage, - "sto": ArrayPackage, - "wel": ListPackage, - # gwt - "dsp": ArrayPackage, - "cnc": ListPackage, - "ist": ArrayPackage, - "mst": ArrayPackage, - "src": ListPackage, - # gwe - "cnd": ArrayPackage, - "est": ArrayPackage, - "cpt": ListPackage, - "esl": ListPackage, - # prt - "mip": ArrayPackage, - } - self.allow_convergence = True self._shape = None self._size = None @@ -186,7 +156,7 @@ def __init__(self, mf6, name): self._usertonode = None self._iteration = 0 - super().__init__(mf6, name, pkg_types) + super().__init__(mf6, name) def __repr__(self): s = f"{self.name}, " diff --git a/modflowapi/extensions/apisimulation.py b/modflowapi/extensions/apisimulation.py index 7916ab3..d7392d3 100644 --- a/modflowapi/extensions/apisimulation.py +++ b/modflowapi/extensions/apisimulation.py @@ -315,7 +315,7 @@ def load(mf6): if idp_names[ix] ] - tmpmdl = ApiMbase(mf6, "", {}) + tmpmdl = ApiMbase(mf6, "") solution_names = list(set(solution_names)) solution_dict = {} for name in solution_names: diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index 62c577a..6a4e6d5 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -3,7 +3,7 @@ import xmipy.errors -class ListInput(object): +class ListInput: """ Data object for storing pointers and working with list based input data @@ -15,24 +15,30 @@ class ListInput(object): optional list of variable addresses mf6 : ModflowApi, None optional ModflowApi object + spd : bool + flag to indicate if the block is loading stress period data or other + list based block data. """ - def __init__(self, parent, var_addrs=None, mf6=None): + def __init__(self, parent, var_addrs=None, mf6=None, spd=True): self.parent = parent + self.var_addrs = var_addrs if self.parent is not None: - self.var_addrs = self.parent.var_addrs + if var_addrs is None: + self.var_addrs = self.parent.var_addrs self.mf6 = self.parent.model.mf6 else: if var_addrs is None or mf6 is None: raise AssertionError( "var_addrs and mf6 must be supplied if parent is None" ) - self.var_addrs = var_addrs + self.mf6 = mf6 self._ptrs = {} + self._compound_ptrs = {} self._nodevars = ("nodelist", "nexg", "maxats") - self._boundvars = ("bound",) + self._bound = "bound" self._maxbound = [ 0, @@ -43,9 +49,12 @@ def __init__(self, parent, var_addrs=None, mf6=None): self._naux = [ 0, ] + self._auxvar_name = "auxvar" self._auxnames = [] self._dtype = [] self._reduced_to_var_addr = {} + + self._spd = spd if self.parent._idm_enabled: for var in ("BOUND", "AUXVAR"): self.var_addrs.pop( @@ -60,11 +69,10 @@ def __init__(self, parent, var_addrs=None, mf6=None): "AUXVAR_IDM", self.parent.model.name, self.parent.pkg_name ) ) - self._set_stress_period_data_idm() - else: - self._set_stress_period_data() - def _set_stress_period_data_idm(self): + self._set_data() + + def _set_data(self): """ Method to set stress period data variable pointers to the _ptrs dictionary. Uses IDM updates instead of bound to access variable @@ -72,95 +80,80 @@ def _set_stress_period_data_idm(self): """ # for now we need to add self.parent._bound_vars data to var_addrs for var_addr in self.var_addrs: - try: - values = self.mf6.get_value_ptr(var_addr) - except xmipy.errors.InputError: - if self._naux[0] > 0: - values = self.mf6.get_value(var_addr) - else: + if ":" in var_addr: + addr_pieces = var_addr.split("/") + compound = addr_pieces[-1] + addr_pieces = addr_pieces[:-1] + reduced, ctype, ptr_var = [i.lower() for i in compound.split(":")] + addr_pieces.append(ptr_var.upper()) + var_addr = "/".join(addr_pieces) + + try: + values = self.mf6.get_value_ptr(var_addr) + except xmipy.errors.InputError: continue - reduced = var_addr.split("/")[-1].lower() - if reduced in ("maxbound", "nbound"): - setattr(self, f"_{reduced}", values) - elif reduced in ("nexg", "maxats"): - setattr(self, "_maxbound", values) - setattr(self, "_nbound", values) - elif reduced in ("naux",): - setattr(self, "_naux", values) - elif reduced in ("auxname_cst"): - setattr(self, "_auxnames", list(values)) - else: - self._ptrs[reduced] = values - self._reduced_to_var_addr[reduced] = var_addr - if reduced in self.parent._bound_vars: - typ_str = values.dtype.str - dtype = (reduced, typ_str) - self._dtype.append(dtype) - elif reduced in self._nodevars: - dtype = (reduced, "O") - self._dtype.append(dtype) - elif reduced == "auxvar_idm": - if self._naux == 0: - continue - else: - for ix in range(self._naux[0]): - typ_str = values.dtype.str - dtype = (self._auxnames[ix], typ_str) - self._dtype.append(dtype) + if reduced in ("ndiv",): + nbound = self._special_condition_to_values(ctype, values) + setattr(self, "_maxbound", [len(values),]) + setattr(self, "_nbound", [nbound,]) else: + self._compound_ptrs[ptr_var] = values + self._ptrs[reduced] = f"{ctype}:{ptr_var}" + typ_str = values.dtype.str dtype = (reduced, typ_str) self._dtype.append(dtype) + else: + try: + values = self.mf6.get_value_ptr(var_addr) + except xmipy.errors.InputError: + if self._naux[0] > 0: + values = self.mf6.get_value(var_addr) + else: + continue - def _set_stress_period_data(self): - """ - Method to set stress period data variable pointers to the _ptrs - dictionary - - PENDING DEPRECATION AND REPLACEMENT by _set_stress_period_data_idm() - """ - for var_addr in self.var_addrs: - try: - values = self.mf6.get_value_ptr(var_addr) - except xmipy.errors.InputError: - if self._naux[0] > 0: - values = self.mf6.get_value(var_addr) + reduced = var_addr.split("/")[-1].lower() + if reduced in ("maxbound", "nbound"): + setattr(self, f"_{reduced}", values) + if not self._spd and reduced == "maxbound": + self._nbound = values + elif reduced in ("nexg", "maxats",): + setattr(self, "_maxbound", values) + setattr(self, "_nbound", values) + elif reduced in ("naux",): + setattr(self, "_naux", values) + elif reduced in ("auxname_cst"): + setattr(self, "_auxnames", list(values)) else: - continue - reduced = var_addr.split("/")[-1].lower() - if reduced in ("maxbound", "nbound"): - setattr(self, f"_{reduced}", values) - elif reduced in ("nexg", "maxats"): - setattr(self, "_maxbound", values) - setattr(self, "_nbound", values) - elif reduced in ("naux",): - setattr(self, "_naux", values) - elif reduced in ("auxname_cst"): - setattr(self, "_auxnames", list(values)) - else: - self._ptrs[reduced] = values - self._reduced_to_var_addr[reduced] = var_addr - if reduced in self._boundvars: - for name in self.parent._bound_vars: + self._ptrs[reduced] = values + self._reduced_to_var_addr[reduced] = var_addr + if reduced == self._bound: + # retain this method for advanced packages + for name in self.parent._bound_vars: + typ_str = values.dtype.str + dtype = (name, typ_str) + self._dtype.append(dtype) + if reduced in self.parent._bound_vars: typ_str = values.dtype.str - dtype = (name, typ_str) + dtype = (reduced, typ_str) self._dtype.append(dtype) - elif reduced in self._nodevars: - dtype = (reduced, "O") - self._dtype.append(dtype) - elif reduced == "auxvar": - if self._naux == 0: - continue + elif reduced in self._nodevars: + dtype = (reduced, "O") + self._dtype.append(dtype) + elif "auxvar" in reduced: + self._auxvar_name = reduced # == "auxvar_idm": + if self._naux == 0: + continue + else: + for ix in range(self._naux[0]): + typ_str = values.dtype.str + dtype = (self._auxnames[ix], typ_str) + self._dtype.append(dtype) else: - for ix in range(self._naux[0]): - typ_str = values.dtype.str - dtype = (self._auxnames[ix], typ_str) - self._dtype.append(dtype) - else: - typ_str = values.dtype.str - dtype = (reduced, typ_str) - self._dtype.append(dtype) + typ_str = values.dtype.str + dtype = (reduced, typ_str) + self._dtype.append(dtype) def _ptr_to_recarray(self): """ @@ -174,23 +167,33 @@ def _ptr_to_recarray(self): return recarray = np.recarray((self._nbound[0],), self._dtype) for name, ptr in self._ptrs.items(): - if "auxvar" in name and self._naux[0] == 0: + if name == self._auxvar_name and self._naux[0] == 0: continue - values = np.copy(ptr) - if name in self._boundvars: - # note: block slated for deprecation + + if isinstance(ptr, str): + # special condition and not a real ptr + ctype, ptr_name = ptr.split(":") + ptr_vals = self._compound_ptrs[ptr_name] + values = self._special_condition_to_values(ctype, ptr_vals) + else: + values = np.copy(ptr) + + if name == self._bound: + # note: keep block around for advanced packages for ix, nm in enumerate(self.parent._bound_vars): - bnd_values = values[0 : self._nbound[0], ix] - recarray[nm][0 : self._nbound[0]] = bnd_values + bnd_values = values[0: self._nbound[0], ix] + recarray[nm][0: self._nbound[0]] = bnd_values + elif name in self.parent._bound_vars and self.parent._idm_enabled: - # new IDM simplification method - bnd_values = values[0 : self._nbound[0]].ravel() - recarray[name][0 : self._nbound[0]] = bnd_values - elif "auxvar" in name: + # IDM simplification method + bnd_values = values[0: self._nbound[0]].ravel() + recarray[name][0: self._nbound[0]] = bnd_values + + elif name == self._auxvar_name: for ix in range(self._naux[0]): nm = self._auxnames[ix] - aux_values = values[0 : self._nbound[0], ix] - recarray[nm][0 : self._nbound[0]] = aux_values + aux_values = values[0: self._nbound[0], ix] + recarray[nm][0: self._nbound[0]] = aux_values elif name == "auxname_cst": pass @@ -204,8 +207,11 @@ def _ptr_to_recarray(self): zip(*np.unravel_index(values, self.parent.model.shape)) ) - values = values[0 : self._nbound[0]] - recarray[name][0 : self._nbound[0]] = values + elif name in ("idv", "divreach"): + values -= 1 + + values = values[0: self._nbound[0]] + recarray[name][0: self._nbound[0]] = values return recarray @@ -220,11 +226,12 @@ def _recarray_to_ptr(self, recarray): numpy recarray of stress period data """ + if recarray is None: self._nbound[0] = 0 return - if len(recarray) != self._nbound: + if len(recarray) != self._nbound[0]: if len(recarray) > self._maxbound[0]: raise AssertionError( f"Length of stresses ({len(recarray)},) cannot be larger " @@ -235,7 +242,10 @@ def _recarray_to_ptr(self, recarray): if len(recarray) == 0: return + visited = [] for name in recarray.dtype.names: + if name in visited: + continue if name in self._nodevars: multi_index = tuple( np.array([list(i) for i in recarray[name]]).T @@ -244,32 +254,110 @@ def _recarray_to_ptr(self, recarray): multi_index, self.parent.model.shape ) recarray[name] = self.parent.model.usertonode[nodes] + 1 + visited.append(name) + + if name in ("divreach",): + self._ptrs[name][0: self._nbound[0]] = recarray[name].ravel() + 1 + visited.append(name) + + elif self._bound in self._ptrs and name in self.parent._bound_vars: + # Check for bound to support advanced packages + idx = self.parent._bound_vars.index(name) + self._ptrs[self._bound[0]][0: self._nbound[0], idx] = recarray[name] + visited.append(name) - if name in self.parent._bound_vars: - if "bound" in self._ptrs: - # Block slated for deprecation after IDM inclusion - idx = self.parent._bound_vars.index(name) - bname = "bound" - self._ptrs[bname][0 : self._nbound[0], idx] = recarray[ - name - ] - elif self.parent._idm_enabled: - # new IDM simplification - self._ptrs[name][0 : self._nbound[0]] = recarray[ - name - ].ravel() - else: - pass elif name in self._auxnames: - ptr_name = "auxvar" - if self.parent._idm_enabled: - ptr_name += "_idm" + ptr_name = self._auxvar_name idx = self._auxnames.index(name) - self._ptrs[ptr_name][0 : self._nbound[0], idx] = recarray[name] + self._ptrs[ptr_name][0: self._nbound[0], idx] = recarray[name] + visited.append(name) + elif name == "auxname_cst": pass + else: - self._ptrs[name][0 : self._nbound[0]] = recarray[name].ravel() + if isinstance(self._ptrs[name], str): + visited = self._special_condition_to_ptr(recarray, name, visited) + else: + self._ptrs[name][0: self._nbound[0]] = recarray[name].ravel() + visited.append(name) + + def _special_condition_to_values(self, ctype, inval): + """ + + Parameters + ---------- + sp_cond + + Returns + ------- + """ + functions = { + "range": lambda x: np.arange(0, int(x), dtype=int), + "count_nonzero": lambda x: np.count_nonzero(x), + "where_idx": lambda x: np.array(np.where(x)[0]), + "where_val": lambda x: x[x != 0], + } + ctype = ctype.lower() + if ctype in ("range",): + inval = inval[0] + + func = functions[ctype] + outval = func(inval) + return outval + + def _special_condition_to_ptr(self, recarray, name, visited): + """ + + Parameters + + + Returns + ------- + + """ + functions = { + "range": lambda x: [len(x),], + } + ctype, ptr_name = self._ptrs[name].split(":") + if "where" in ctype: + idx_name = None + val_name = None + if ctype == "where_idx": + idx_name = name + for k, v in self._ptrs.items(): + if isinstance(v, str): + if v == f"where_val:{ptr_name}": + val_name = k + break + + elif ctype == "where_val": + val_name = name + for k, v in self._ptrs.items(): + if isinstance(v, str): + if v == f"where_idx:{ptr_name}": + idx_name = k + break + else: + return + + if idx_name is None or val_name is None: + return + + idx = list(recarray[idx_name].astype(int)) + vals = recarray[val_name].ravel() + if val_name in ("idv",): + vals += 1 + self._compound_ptrs[ptr_name][idx] = vals + visited.extend([idx_name, val_name]) + + else: + func = functions[ctype] + values = func(recarray[name]) + self._compound_ptrs[ptr_name][:] = values[:] + visited.append(name) + + return visited def __getitem__(self, item): recarray = self._ptr_to_recarray() diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py new file mode 100644 index 0000000..346e075 --- /dev/null +++ b/modflowapi/extensions/datamodel.py @@ -0,0 +1,295 @@ +""" +Centralized location to store the "data model"/relationship trees for packages +blocks, and input variables that are used by the modflowapi.extensions code +""" + +gridshape = { + "dis": ["nlay", "nrow", "ncol"], + "disu": ["nlay", "ncpl",], +} + + +# Note: HFB variables are not accessible in the memory manager 10/7/2022 +pkgvars = { + "dis": ["top", "bot", "area", "idomain"], + "chd": [ + "nbound", + "maxbound", + "nodelist", + ("bound", ("head",)), + "naux", + "auxname_cst", + "auxvar", + ], + "drn": [ + "nbound", + "maxbound", + "nodelist", + ( + "bound", + ( + "elev", + "cond", + ), + ), + "naux", + "auxname_cst", + "auxvar", + ], + "evt": [ + "nbound", + "maxbound", + "nodelist", + ( + "bound", + ( + "surface", + "rate", + "depth", + ), + ), + # "pxdp:NSEG", "petm:NSEG" + "naux", + "auxname_cst", + "auxvar", + ], + "ghb": [ + "nbound", + "maxbound", + "nodelist", + ( + "bound", + ( + "bhead", + "cond", + ), + ), + "naux", + "auxname_cst", + "auxvar", + ], + "ic": ["strt"], + "npf": ["k11", "k22", "k33", "angle1", "angle2", "angle3", "icelltype"], + "rch": [ + "maxbound", + "nbound", + "nodelist", + ("bound", ("recharge",)), + "naux", + "auxname_cst", + "auxvar", + ], + "riv": [ + "maxbound", + "nbound", + "nodelist", + ("bound", ("stage", "cond", "rbot")), + "naux", + "auxname_cst", + "auxvar", + ], + "sto": ["iconvert", "ss", "sy"], + "wel": [ + "maxbound", + "nbound", + "nodelist", + ("bound", ("q",)), + "naux", + "auxname_cst", + "auxvar", + ], + # gwe model + "cnd": ["alh", "alv", "ath1", "ath2", "atv", "kts"], + "est": ["porosity", "decay", "cps", "rhos"], + "cpt": [ + "maxbound", + "nbound", + "nodelist", + ("bound", ("temp",)), + "naux", + "auxname_cst", + "auxvar", + ], + "esl": [ + "maxbound", + "nbound", + "nodelist", + ("bound", ("senerrate",)), + "naux", + "auxname_cst", + "auxvar", + ], + # gwt model + "dsp": ["diffc", "alh", "alv", "ath1", "ath2", "atv"], + "cnc": [ + "maxbound", + "nbound", + "nodelist", + ("bound", ("conc",)), + "naux", + "auxname_cst", + "auxvar", + ], + "ist": [ + "cim", + "thtaim", + "zetaim", + "decay", + "decay_sorbed", + "bulk_density", + "distcoef", + ], + "mst": ["porosity", "decay", "decay_sorbed", "bulk_density", "distcoef"], + "src": [ + "maxbound", + "nbound", + "nodelist", + ("bound", ("smassrate",)), + "naux", + "auxname_cst", + "auxvar", + ], + # prt model + "mip": ["porosity", "retfactor", "izone"], + # exchange model + "gwf-gwf": ["nexg", "nodem1", "nodem2", "cl1", "cl2", "ihc", "hwva"], + "gwt-gwt": ["nexg", "nodem1", "nodem2", "cl1", "cl2", "ihc", "hwva"], + "gwe-gwe": ["nexg", "nodem1", "nodem2", "cl1", "cl2", "ihc", "hwva"], + # simulation + "ats": [ + "maxats", + "iperats", + "dt0", + "dtmin", + "dtmax", + "dtadj", + "dtfailadj", + ], + "tdis": [ + "nper", + "itmuni", + "kper", + "kstp", + "delt", + "pertim", + "totim,", + "perlen", + "nstp", + "tsmult", + ], + # solution package + "sln-ims": [ + "mxiter", + "dvclose", + "gamma", + "theta", + "akappa", + "amomentum", + "numtrack", + "btol", + "breduc", + "res_lim", + ], + "ims": [ + "niterc", + "dvclose", + "rclose", + "relax", + "ipc", + "droptol", + "north", + "iscl", + "iord", + ], + "sln-ems": [ + "icnvg", + "ttsoln", + ], +} + + +adv_pkgvars = { + "sfr": + {"packagedata":[ + "maxbound", + ( + "ifno:range:maxbound", + "nodelist", + "length", + "width", + "slope", + "strtop", + "bthick", + "hk", + "rough", + "nconnreach", + "ustrf", + "ndiv" + ), + ], + "diversions":[ + "ndiv:count_nonzero:ndiv", + ( + "ifno:where_idx:ndiv", + "idv:where_val:ndiv", + "divreach", # iconr + ), + ], + "perioddata":[ + "maxbound", + "nbound", + ("bound", + ( + "ifno", + "sfrsetting", + "setting_0", + "setting_1" + ), + ) + ], + } +} + + +def get_package_type(pkg_type): + from .pakbase import ArrayPackage, ListPackage, ScalarPackage, AdvancedPackage + from .advpaks import SfrPakage + pkg_types = { + "dis": ArrayPackage, + "chd": ListPackage, + "drn": ListPackage, + "evt": ListPackage, + "ghb": ListPackage, + "ic": ArrayPackage, + "npf": ArrayPackage, + "rch": ListPackage, + "riv": ListPackage, + "sto": ArrayPackage, + "wel": ListPackage, + # advanced + # "sfr": SfrPakage, + # "lak": None, + # "uzf": None, + # "maw": None, + # "csub": None, + # gwt + "dsp": ArrayPackage, + "cnc": ListPackage, + "ist": ArrayPackage, + "mst": ArrayPackage, + "src": ListPackage, + # gwe + "cnd": ArrayPackage, + "est": ArrayPackage, + "cpt": ListPackage, + "esl": ListPackage, + # prt + "mip": ArrayPackage, + # sim_level pkgs + "tdis": ScalarPackage, + "ats": ListPackage, + } + if pkg_type in pkg_types: + return pkg_types[pkg_type] + else: + return AdvancedPackage \ No newline at end of file diff --git a/modflowapi/extensions/pakbase.py b/modflowapi/extensions/pakbase.py index 9d5a7b7..fbc86c7 100644 --- a/modflowapi/extensions/pakbase.py +++ b/modflowapi/extensions/pakbase.py @@ -1,204 +1,7 @@ import numpy as np from .data import AdvancedInput, ArrayInput, ListInput, ScalarInput - -# Note: HFB variables are not accessible in the memory manager 10/7/2022 -pkgvars = { - "dis": ["top", "bot", "area", "idomain"], - "chd": [ - "nbound", - "maxbound", - "nodelist", - ("bound", ("head",)), - "naux", - "auxname_cst", - "auxvar", - ], - "drn": [ - "nbound", - "maxbound", - "nodelist", - ( - "bound", - ( - "elev", - "cond", - ), - ), - "naux", - "auxname_cst", - "auxvar", - ], - "evt": [ - "nbound", - "maxbound", - "nodelist", - ( - "bound", - ( - "surface", - "rate", - "depth", - ), - ), - # "pxdp:NSEG", "petm:NSEG" - "naux", - "auxname_cst", - "auxvar", - ], - "ghb": [ - "nbound", - "maxbound", - "nodelist", - ( - "bound", - ( - "bhead", - "cond", - ), - ), - "naux", - "auxname_cst", - "auxvar", - ], - "ic": ["strt"], - "npf": ["k11", "k22", "k33", "angle1", "angle2", "angle3", "icelltype"], - "rch": [ - "maxbound", - "nbound", - "nodelist", - ("bound", ("recharge",)), - "naux", - "auxname_cst", - "auxvar", - ], - "riv": [ - "maxbound", - "nbound", - "nodelist", - ("bound", ("stage", "cond", "rbot")), - "naux", - "auxname_cst", - "auxvar", - ], - "sto": ["iconvert", "ss", "sy"], - "wel": [ - "maxbound", - "nbound", - "nodelist", - ("bound", ("q",)), - "naux", - "auxname_cst", - "auxvar", - ], - # gwe model - "cnd": ["alh", "alv", "ath1", "ath2", "atv", "kts"], - "est": ["porosity", "decay", "cps", "rhos"], - "cpt": [ - "maxbound", - "nbound", - "nodelist", - ("bound", ("temp",)), - "naux", - "auxname_cst", - "auxvar", - ], - "esl": [ - "maxbound", - "nbound", - "nodelist", - ("bound", ("senerrate",)), - "naux", - "auxname_cst", - "auxvar", - ], - # gwt model - "dsp": ["diffc", "alh", "alv", "ath1", "ath2", "atv"], - "cnc": [ - "maxbound", - "nbound", - "nodelist", - ("bound", ("conc",)), - "naux", - "auxname_cst", - "auxvar", - ], - "ist": [ - "cim", - "thtaim", - "zetaim", - "decay", - "decay_sorbed", - "bulk_density", - "distcoef", - ], - "mst": ["porosity", "decay", "decay_sorbed", "bulk_density", "distcoef"], - "src": [ - "maxbound", - "nbound", - "nodelist", - ("bound", ("smassrate",)), - "naux", - "auxname_cst", - "auxvar", - ], - # prt model - "mip": ["porosity", "retfactor", "izone"], - # exchange model - "gwf-gwf": ["nexg", "nodem1", "nodem2", "cl1", "cl2", "ihc", "hwva"], - "gwt-gwt": ["nexg", "nodem1", "nodem2", "cl1", "cl2", "ihc", "hwva"], - "gwe-gwe": ["nexg", "nodem1", "nodem2", "cl1", "cl2", "ihc", "hwva"], - # simulation - "ats": [ - "maxats", - "iperats", - "dt0", - "dtmin", - "dtmax", - "dtadj", - "dtfailadj", - ], - "tdis": [ - "nper", - "itmuni", - "kper", - "kstp", - "delt", - "pertim", - "totim,", - "perlen", - "nstp", - "tsmult", - ], - # solution package - "sln-ims": [ - "mxiter", - "dvclose", - "gamma", - "theta", - "akappa", - "amomentum", - "numtrack", - "btol", - "breduc", - "res_lim", - ], - "ims": [ - "niterc", - "dvclose", - "rclose", - "relax", - "ipc", - "droptol", - "north", - "iscl", - "iord", - ], - "sln-ems": [ - "icnvg", - "ttsoln", - ], -} +from .datamodel import pkgvars, adv_pkgvars class PackageBase: @@ -274,6 +77,7 @@ def __init__(self, model, pkg_type, pkg_name, child_type, sim_package): var.upper(), self.model.name, self.pkg_name ) if addr_chk in self.model.mf6.get_input_var_names(): + # change this to use idm self._idm_enabled = True var_addrs.append(addr_chk) @@ -503,6 +307,20 @@ def __init__(self, model, pkg_type, pkg_name, sim_package=False): self._variables = ArrayInput(self) + def _get_var_addrs(self, block): + """ + Method to make variable addresses for advanced packages + + Parameters + ---------- + block + + Returns + ------- + + """ + pass + def __repr__(self): s = f"{self.pkg_type.upper()} Package: {self.pkg_name} \n" s += " Accessible variables include:\n" @@ -695,18 +513,143 @@ class AdvancedPackage(PackageBase): sim_package : bool boolean flag for simulation level packages. Ex. TDIS, IMS """ - - def __init__(self, model, pkg_type, pkg_name, sim_package=False): + def __init__(self, model, pkg_type, pkg_name, sim_package=False): # pass in stress period data here ???? super().__init__( model, pkg_type, pkg_name.upper(), "advanced", sim_package ) + self._idm_enabled = False + self._package_var_addrs = [] + self._sp_var_addrs = [] + self._package_vars = None + self._sp_vars = None + + if pkg_type in adv_pkgvars: + self._adv_var_dict = adv_pkgvars[pkg_type] + + self._set_advanced_variable_addrs("packagedata", "_package_var_addrs") + + if "perioddata" in self._adv_var_dict: + # create variable addresses!!!! + for var in self._adv_var_dict["perioddata"]: + if isinstance(var, tuple): + self._bound_vars = var[-1] + var = var[0] + + var_addr = self.model.mf6.get_var_address( + var.upper(), self.model.name, self.pkg_name + ) + self._sp_var_addrs.append(var_addr) + + self._package_vars = ListInput(self, self._package_var_addrs, spd=False) + self._sp_vars = ListInput(self, self._sp_var_addrs) + def __repr__(self): s = f"{self.pkg_type.upper()} Package: {self.pkg_name} \n" s += " Advanced Package, variables only accessible through\n" s += " get_advanced_var() and set_advanced_var() methods" return s + def _set_advanced_variable_addrs(self, block, attr): + """ + General method for setting advanced variable block addresses + to their attributes. Method is used to reduce code duplication + + Parameters + ---------- + block : str + data block key + attr : str + attribute name + + Returns + ------- + None + """ + var_addrs = [] + if block in self._adv_var_dict: + for var in self._adv_var_dict[block]: + if not isinstance(var, tuple): + var_addrs.append(self._get_advanced_variable_addr(var)) + else: + for v in var: + var_addrs.append(self._get_advanced_variable_addr(v)) + + setattr(self, attr, var_addrs) + + def _get_advanced_variable_addr(self, var_str): + """ + Method to create variable addresses for advanced packages that can + include non-standard logic and processing instructions + + Parameters + ---------- + var_str : str + + Returns + ------- + var_addr : str + """ + s = f"{self.model.name}/{self.pkg_name}/{var_str.upper()}" + return s + + @property + def packagedata(self): + """ + Returns a BlockInput object of the packagedata + """ + return self._package_vars + + @packagedata.setter + def packagedata(self, recarray): + """ + Setter method to update the packagedata + + """ + if self._package_vars is not None: + if isinstance(recarray, np.recarray): + self._package_vars.values = recarray + elif isinstance(recarray, ListInput): + self._package_vars.values = recarray.values + elif recarray is None: + self._package_vars.values = recarray + else: + raise TypeError( + f"{type(recarray)} is not a supported stress_period_data type" + ) + + @property + def maxbound(self): + """ + Returns the "maxbound" value for the stress period + """ + if self._sp_vars is not None: + return self._sp_vars._maxbound[0] + + @property + def stress_period_data(self): + """ + Returns a ListInput object of the current stress_period_data + """ + return self._sp_vars + + @stress_period_data.setter + def stress_period_data(self, recarray): + """ + Setter method to update the current stress_period_data + """ + if self._sp_vars is not None: + if isinstance(recarray, np.recarray): + self._sp_vars.values = recarray + elif isinstance(recarray, ListInput): + self._sp_vars.values = recarray.values + elif recarray is None: + self._sp_vars.values = recarray + else: + raise TypeError( + f"{type(recarray)} is not a supported stress_period_data type" + ) + class ApiSlnPackage(ScalarPackage): """ @@ -767,6 +710,8 @@ def __init__(self, obj, model, pkg_type, pkg_name, sim_package=False): cls_str = "".join(pkg_type.split("-")) cls_str = f"{cls_str[0].upper()}{cls_str[1:]}" + + # should this be a classmethod on each package???? package = type( f"Api{cls_str}Package", (basepackage,), From 460f13d87185906d3d44f892bb7fc22b776fba74 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Tue, 8 Apr 2025 17:20:34 -0700 Subject: [PATCH 03/17] linting with ruff --- modflowapi/extensions/advpaks.py | 17 +++++---- modflowapi/extensions/data.py | 57 ++++++++++++++++++------------ modflowapi/extensions/datamodel.py | 46 ++++++++++++------------ modflowapi/extensions/pakbase.py | 13 +++---- 4 files changed, 73 insertions(+), 60 deletions(-) diff --git a/modflowapi/extensions/advpaks.py b/modflowapi/extensions/advpaks.py index 8d15ce5..6818e0e 100644 --- a/modflowapi/extensions/advpaks.py +++ b/modflowapi/extensions/advpaks.py @@ -18,6 +18,7 @@ class SfrPakage(AdvancedPackage): sim_package : bool boolean flag for simulation level packages. Ex. TDIS, IMS """ + def __init__(self, model, pkg_type, pkg_name, sim_package=False): super().__init__(model, pkg_type, pkg_name, sim_package) @@ -42,9 +43,7 @@ def diversions(self, recarray): elif recarray is None: self._package_vars.values = recarray else: - raise TypeError( - f"{type(recarray)} is not a supported stress_period_data type" - ) + raise TypeError(f"{type(recarray)} is not a supported stress_period_data type") class LakPackage: @@ -122,7 +121,10 @@ class CsubPackage: sim_package : bool boolean flag for simulation level packages. Ex. TDIS, IMS """ - def __init__(self, ): + + def __init__( + self, + ): self.x = None @@ -141,5 +143,8 @@ class MvrPackage: sim_package : bool boolean flag for simulation level packages. Ex. TDIS, IMS """ - def __init__(self, ): - self.x = None \ No newline at end of file + + def __init__( + self, + ): + self.x = None diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index db8ee43..b0fbee7 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -53,11 +53,7 @@ def __init__(self, parent, var_addrs=None, mf6=None, spd=True): self.var_addrs.index(self.mf6.get_var_address(var, self.parent.model.name, self.parent.pkg_name)) ) - self.var_addrs.append( - self.mf6.get_var_address( - "AUXVAR_IDM", self.parent.model.name, self.parent.pkg_name - ) - ) + self.var_addrs.append(self.mf6.get_var_address("AUXVAR_IDM", self.parent.model.name, self.parent.pkg_name)) self._set_data() @@ -84,8 +80,20 @@ def _set_data(self): if reduced in ("ndiv",): nbound = self._special_condition_to_values(ctype, values) - setattr(self, "_maxbound", [len(values),]) - setattr(self, "_nbound", [nbound,]) + setattr( + self, + "_maxbound", + [ + len(values), + ], + ) + setattr( + self, + "_nbound", + [ + nbound, + ], + ) else: self._compound_ptrs[ptr_var] = values self._ptrs[reduced] = f"{ctype}:{ptr_var}" @@ -107,7 +115,10 @@ def _set_data(self): setattr(self, f"_{reduced}", values) if not self._spd and reduced == "maxbound": self._nbound = values - elif reduced in ("nexg", "maxats",): + elif reduced in ( + "nexg", + "maxats", + ): setattr(self, "_maxbound", values) setattr(self, "_nbound", values) elif reduced in ("naux",): @@ -131,7 +142,7 @@ def _set_data(self): dtype = (reduced, "O") self._dtype.append(dtype) elif "auxvar" in reduced: - self._auxvar_name = reduced # == "auxvar_idm": + self._auxvar_name = reduced # == "auxvar_idm": if self._naux == 0: continue else: @@ -170,19 +181,19 @@ def _ptr_to_recarray(self): if name == self._bound: # note: keep block around for advanced packages for ix, nm in enumerate(self.parent._bound_vars): - bnd_values = values[0: self._nbound[0], ix] - recarray[nm][0: self._nbound[0]] = bnd_values + bnd_values = values[0 : self._nbound[0], ix] + recarray[nm][0 : self._nbound[0]] = bnd_values elif name in self.parent._bound_vars and self.parent._idm_enabled: # IDM simplification method - bnd_values = values[0: self._nbound[0]].ravel() - recarray[name][0: self._nbound[0]] = bnd_values + bnd_values = values[0 : self._nbound[0]].ravel() + recarray[name][0 : self._nbound[0]] = bnd_values elif name == self._auxvar_name: for ix in range(self._naux[0]): nm = self._auxnames[ix] - aux_values = values[0: self._nbound[0], ix] - recarray[nm][0: self._nbound[0]] = aux_values + aux_values = values[0 : self._nbound[0], ix] + recarray[nm][0 : self._nbound[0]] = aux_values elif name == "auxname_cst": pass @@ -197,8 +208,8 @@ def _ptr_to_recarray(self): elif name in ("idv", "divreach"): values -= 1 - values = values[0: self._nbound[0]] - recarray[name][0: self._nbound[0]] = values + values = values[0 : self._nbound[0]] + recarray[name][0 : self._nbound[0]] = values return recarray @@ -239,19 +250,19 @@ def _recarray_to_ptr(self, recarray): visited.append(name) if name in ("divreach",): - self._ptrs[name][0: self._nbound[0]] = recarray[name].ravel() + 1 + self._ptrs[name][0 : self._nbound[0]] = recarray[name].ravel() + 1 visited.append(name) elif self._bound in self._ptrs and name in self.parent._bound_vars: # Check for bound to support advanced packages idx = self.parent._bound_vars.index(name) - self._ptrs[self._bound[0]][0: self._nbound[0], idx] = recarray[name] + self._ptrs[self._bound[0]][0 : self._nbound[0], idx] = recarray[name] visited.append(name) elif name in self._auxnames: ptr_name = self._auxvar_name idx = self._auxnames.index(name) - self._ptrs[ptr_name][0: self._nbound[0], idx] = recarray[name] + self._ptrs[ptr_name][0 : self._nbound[0], idx] = recarray[name] visited.append(name) elif name == "auxname_cst": @@ -261,7 +272,7 @@ def _recarray_to_ptr(self, recarray): if isinstance(self._ptrs[name], str): visited = self._special_condition_to_ptr(recarray, name, visited) else: - self._ptrs[name][0: self._nbound[0]] = recarray[name].ravel() + self._ptrs[name][0 : self._nbound[0]] = recarray[name].ravel() visited.append(name) def _special_condition_to_values(self, ctype, inval): @@ -299,7 +310,9 @@ def _special_condition_to_ptr(self, recarray, name, visited): """ functions = { - "range": lambda x: [len(x),], + "range": lambda x: [ + len(x), + ], } ctype, ptr_name = self._ptrs[name].split(":") if "where" in ctype: diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index 346e075..ef2bf73 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -5,7 +5,10 @@ gridshape = { "dis": ["nlay", "nrow", "ncol"], - "disu": ["nlay", "ncpl",], + "disu": [ + "nlay", + "ncpl", + ], } @@ -209,10 +212,10 @@ adv_pkgvars = { - "sfr": - {"packagedata":[ + "sfr": { + "packagedata": [ "maxbound", - ( + ( "ifno:range:maxbound", "nodelist", "length", @@ -224,29 +227,25 @@ "rough", "nconnreach", "ustrf", - "ndiv" - ), - ], - "diversions":[ + "ndiv", + ), + ], + "diversions": [ "ndiv:count_nonzero:ndiv", ( - "ifno:where_idx:ndiv", - "idv:where_val:ndiv", - "divreach", # iconr + "ifno:where_idx:ndiv", + "idv:where_val:ndiv", + "divreach", # iconr ), ], - "perioddata":[ + "perioddata": [ "maxbound", "nbound", - ("bound", - ( - "ifno", - "sfrsetting", - "setting_0", - "setting_1" - ), - ) - ], + ( + "bound", + ("ifno", "sfrsetting", "setting_0", "setting_1"), + ), + ], } } @@ -254,6 +253,7 @@ def get_package_type(pkg_type): from .pakbase import ArrayPackage, ListPackage, ScalarPackage, AdvancedPackage from .advpaks import SfrPakage + pkg_types = { "dis": ArrayPackage, "chd": ListPackage, @@ -288,8 +288,8 @@ def get_package_type(pkg_type): # sim_level pkgs "tdis": ScalarPackage, "ats": ListPackage, - } + } if pkg_type in pkg_types: return pkg_types[pkg_type] else: - return AdvancedPackage \ No newline at end of file + return AdvancedPackage diff --git a/modflowapi/extensions/pakbase.py b/modflowapi/extensions/pakbase.py index 239fdfe..f478c76 100644 --- a/modflowapi/extensions/pakbase.py +++ b/modflowapi/extensions/pakbase.py @@ -473,6 +473,7 @@ class AdvancedPackage(PackageBase): sim_package : bool boolean flag for simulation level packages. Ex. TDIS, IMS """ + def __init__(self, model, pkg_type, pkg_name, sim_package=False): super().__init__(model, pkg_type, pkg_name.upper(), "advanced", sim_package) @@ -494,9 +495,7 @@ def __init__(self, model, pkg_type, pkg_name, sim_package=False): self._bound_vars = var[-1] var = var[0] - var_addr = self.model.mf6.get_var_address( - var.upper(), self.model.name, self.pkg_name - ) + var_addr = self.model.mf6.get_var_address(var.upper(), self.model.name, self.pkg_name) self._sp_var_addrs.append(var_addr) self._package_vars = ListInput(self, self._package_var_addrs, spd=False) @@ -572,9 +571,7 @@ def packagedata(self, recarray): elif recarray is None: self._package_vars.values = recarray else: - raise TypeError( - f"{type(recarray)} is not a supported stress_period_data type" - ) + raise TypeError(f"{type(recarray)} is not a supported stress_period_data type") @property def maxbound(self): @@ -604,9 +601,7 @@ def stress_period_data(self, recarray): elif recarray is None: self._sp_vars.values = recarray else: - raise TypeError( - f"{type(recarray)} is not a supported stress_period_data type" - ) + raise TypeError(f"{type(recarray)} is not a supported stress_period_data type") class ApiSlnPackage(ScalarPackage): From c2794c2be015d011d937b7060ee11e83bcd3e069 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Tue, 8 Apr 2025 17:22:58 -0700 Subject: [PATCH 04/17] ruff fix import order --- modflowapi/extensions/advpaks.py | 5 +++-- modflowapi/extensions/apimodel.py | 2 +- modflowapi/extensions/datamodel.py | 3 +-- modflowapi/extensions/pakbase.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modflowapi/extensions/advpaks.py b/modflowapi/extensions/advpaks.py index 6818e0e..0b6aa25 100644 --- a/modflowapi/extensions/advpaks.py +++ b/modflowapi/extensions/advpaks.py @@ -1,7 +1,8 @@ -from .pakbase import AdvancedPackage -from .data import ListInput import numpy as np +from .data import ListInput +from .pakbase import AdvancedPackage + class SfrPakage(AdvancedPackage): """ diff --git a/modflowapi/extensions/apimodel.py b/modflowapi/extensions/apimodel.py index 4ec2572..c6c9860 100644 --- a/modflowapi/extensions/apimodel.py +++ b/modflowapi/extensions/apimodel.py @@ -1,7 +1,7 @@ import numpy as np +from .datamodel import get_package_type, gridshape from .pakbase import AdvancedPackage, ArrayPackage, ListPackage, package_factory -from .datamodel import gridshape, get_package_type class ApiMbase: diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index ef2bf73..baa53a1 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -251,8 +251,7 @@ def get_package_type(pkg_type): - from .pakbase import ArrayPackage, ListPackage, ScalarPackage, AdvancedPackage - from .advpaks import SfrPakage + from .pakbase import AdvancedPackage, ArrayPackage, ListPackage, ScalarPackage pkg_types = { "dis": ArrayPackage, diff --git a/modflowapi/extensions/pakbase.py b/modflowapi/extensions/pakbase.py index f478c76..505c383 100644 --- a/modflowapi/extensions/pakbase.py +++ b/modflowapi/extensions/pakbase.py @@ -1,7 +1,7 @@ import numpy as np from .data import AdvancedInput, ArrayInput, ListInput, ScalarInput -from .datamodel import pkgvars, adv_pkgvars +from .datamodel import adv_pkgvars, pkgvars class PackageBase: From 1b4424ee124e6a1ac4f2ab662a549cd39985e5cf Mon Sep 17 00:00:00 2001 From: jlarsen Date: Tue, 8 Apr 2025 17:30:31 -0700 Subject: [PATCH 05/17] fix pre-idm bound condition in _ptr_to_recarray --- modflowapi/extensions/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index b0fbee7..fa5cb34 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -256,7 +256,7 @@ def _recarray_to_ptr(self, recarray): elif self._bound in self._ptrs and name in self.parent._bound_vars: # Check for bound to support advanced packages idx = self.parent._bound_vars.index(name) - self._ptrs[self._bound[0]][0 : self._nbound[0], idx] = recarray[name] + self._ptrs[self._bound][0 : self._nbound[0], idx] = recarray[name] visited.append(name) elif name in self._auxnames: From 863d98a2f384721f813e95fb1fb623eefa28926d Mon Sep 17 00:00:00 2001 From: jlarsen Date: Tue, 8 Apr 2025 17:37:08 -0700 Subject: [PATCH 06/17] fix for pre-idm data setting routines --- modflowapi/extensions/data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index fa5cb34..75b7b38 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -272,7 +272,8 @@ def _recarray_to_ptr(self, recarray): if isinstance(self._ptrs[name], str): visited = self._special_condition_to_ptr(recarray, name, visited) else: - self._ptrs[name][0 : self._nbound[0]] = recarray[name].ravel() + shape = self._ptrs[name][0 : self._nbound[0]].shape + self._ptrs[name][0 : self._nbound[0]] = recarray[name].reshape(shape) visited.append(name) def _special_condition_to_values(self, ctype, inval): From df2fb246210abaa8d03ce9da7afa02a2d74e2d89 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Tue, 8 Apr 2025 17:40:50 -0700 Subject: [PATCH 07/17] try no raveling or reshaping for pointer setting (pre-idm support) --- modflowapi/extensions/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index 75b7b38..131ab4d 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -272,8 +272,8 @@ def _recarray_to_ptr(self, recarray): if isinstance(self._ptrs[name], str): visited = self._special_condition_to_ptr(recarray, name, visited) else: - shape = self._ptrs[name][0 : self._nbound[0]].shape - self._ptrs[name][0 : self._nbound[0]] = recarray[name].reshape(shape) + # shape = self._ptrs[name][0 : self._nbound[0]].shape + self._ptrs[name][0 : self._nbound[0]] = recarray[name] visited.append(name) def _special_condition_to_values(self, ctype, inval): From 8e7db603ec29e92be0d41714d3ec91e69fe5a999 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Tue, 8 Apr 2025 17:51:06 -0700 Subject: [PATCH 08/17] remove visited flag from nodevars --- modflowapi/extensions/data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index 131ab4d..83df9d3 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -247,7 +247,6 @@ def _recarray_to_ptr(self, recarray): multi_index = tuple(np.array([list(i) for i in recarray[name]]).T) nodes = np.ravel_multi_index(multi_index, self.parent.model.shape) recarray[name] = self.parent.model.usertonode[nodes] + 1 - visited.append(name) if name in ("divreach",): self._ptrs[name][0 : self._nbound[0]] = recarray[name].ravel() + 1 From f0273fe9f9768938d41939c80f6872937da08a88 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Wed, 9 Apr 2025 10:02:38 -0700 Subject: [PATCH 09/17] update if to elif statement for "bound" variable setting --- modflowapi/extensions/data.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index 83df9d3..655e50b 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -134,7 +134,7 @@ def _set_data(self): typ_str = values.dtype.str dtype = (name, typ_str) self._dtype.append(dtype) - if reduced in self.parent._bound_vars: + elif reduced in self.parent._bound_vars: typ_str = values.dtype.str dtype = (reduced, typ_str) self._dtype.append(dtype) @@ -267,11 +267,13 @@ def _recarray_to_ptr(self, recarray): elif name == "auxname_cst": pass + elif name == self._bound: + pass + else: if isinstance(self._ptrs[name], str): visited = self._special_condition_to_ptr(recarray, name, visited) else: - # shape = self._ptrs[name][0 : self._nbound[0]].shape self._ptrs[name][0 : self._nbound[0]] = recarray[name] visited.append(name) From 47342418af983bc41fcfd8d03eb914e1c4186fc0 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Wed, 9 Apr 2025 15:18:55 -0700 Subject: [PATCH 10/17] Extend AdvancedPackage support for SfrPakage and UzfPackage --- modflowapi/extensions/advpaks.py | 2 +- modflowapi/extensions/datamodel.py | 29 ++++++++++++++++++--- modflowapi/extensions/pakbase.py | 42 ++++++++++++++++-------------- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/modflowapi/extensions/advpaks.py b/modflowapi/extensions/advpaks.py index 0b6aa25..96e9ea8 100644 --- a/modflowapi/extensions/advpaks.py +++ b/modflowapi/extensions/advpaks.py @@ -87,7 +87,7 @@ def __init__(self, model, pkg_type, pkg_name, sim_package=False): super().__init__(model, pkg_type, pkg_name, sim_package) -class UzfPackage: +class UzfPackage(AdvancedPackage): """ Container for UZF and UZF like packages diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index baa53a1..fe4eb0d 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -246,12 +246,35 @@ ("ifno", "sfrsetting", "setting_0", "setting_1"), ), ], - } + }, + "uzf": { + "packagedata": [ + "maxbound", + ( + "ifno:range:maxbound", + "nodelist", + "landflag", + "ivertcon", + "surfdep", + "vks", + "thtr", + "thts", + "thti", + "eps", + ), + ], + "perioddata": [ + "maxbound", + "nbound", + ("bound", ("ifno:range:maxbound", "finf", "pet", "extdp", "extwc", "ha", "hroot", "rootact")), + ], + }, } def get_package_type(pkg_type): from .pakbase import AdvancedPackage, ArrayPackage, ListPackage, ScalarPackage + from .advpaks import SfrPakage, UzfPackage pkg_types = { "dis": ArrayPackage, @@ -266,9 +289,9 @@ def get_package_type(pkg_type): "sto": ArrayPackage, "wel": ListPackage, # advanced - # "sfr": SfrPakage, + "sfr": SfrPakage, + "uzf": UzfPackage, # "lak": None, - # "uzf": None, # "maw": None, # "csub": None, # gwt diff --git a/modflowapi/extensions/pakbase.py b/modflowapi/extensions/pakbase.py index 505c383..a274594 100644 --- a/modflowapi/extensions/pakbase.py +++ b/modflowapi/extensions/pakbase.py @@ -279,20 +279,6 @@ def __init__(self, model, pkg_type, pkg_name, sim_package=False): self._variables = ArrayInput(self) - def _get_var_addrs(self, block): - """ - Method to make variable addresses for advanced packages - - Parameters - ---------- - block - - Returns - ------- - - """ - pass - def __repr__(self): s = f"{self.pkg_type.upper()} Package: {self.pkg_name} \n" s += " Accessible variables include:\n" @@ -492,11 +478,29 @@ def __init__(self, model, pkg_type, pkg_name, sim_package=False): # create variable addresses!!!! for var in self._adv_var_dict["perioddata"]: if isinstance(var, tuple): - self._bound_vars = var[-1] - var = var[0] - - var_addr = self.model.mf6.get_var_address(var.upper(), self.model.name, self.pkg_name) - self._sp_var_addrs.append(var_addr) + # todo: check if there's a compound variable in bound + use_bound = True + for v in var[-1]: + if ":" in v: + use_bound = False + + if use_bound: + self._bound_vars = var[-1] + var = var[0] + else: + for v in var[-1]: + if ":" in v: + tmp = v.split(":")[0] + self._bound_vars.append(tmp) + else: + self._bound_vars.append(v) + var_addr = self._get_advanced_variable_addr(v) + self._sp_var_addrs.append(var_addr) + var = None + + if var is not None: + var_addr = self.model.mf6.get_var_address(var.upper(), self.model.name, self.pkg_name) + self._sp_var_addrs.append(var_addr) self._package_vars = ListInput(self, self._package_var_addrs, spd=False) self._sp_vars = ListInput(self, self._sp_var_addrs) From 67bb6c73328ff8ee232aa42921a51fe0d701896e Mon Sep 17 00:00:00 2001 From: jlarsen Date: Wed, 9 Apr 2025 15:32:24 -0700 Subject: [PATCH 11/17] updates to advanced packages --- modflowapi/extensions/datamodel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index fe4eb0d..5752912 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -291,7 +291,7 @@ def get_package_type(pkg_type): # advanced "sfr": SfrPakage, "uzf": UzfPackage, - # "lak": None, + # "lak": abc, # "maw": None, # "csub": None, # gwt From fc752f68a5cf2e2b75326a83b758e8da7b3c32b1 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Wed, 9 Apr 2025 15:34:59 -0700 Subject: [PATCH 12/17] linting --- modflowapi/extensions/datamodel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index 5752912..c39f712 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -273,8 +273,8 @@ def get_package_type(pkg_type): - from .pakbase import AdvancedPackage, ArrayPackage, ListPackage, ScalarPackage from .advpaks import SfrPakage, UzfPackage + from .pakbase import AdvancedPackage, ArrayPackage, ListPackage, ScalarPackage pkg_types = { "dis": ArrayPackage, From d0f7d0dadb3b1eb3f5669bfa2e2e0ced7db72578 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Thu, 10 Apr 2025 17:19:59 -0700 Subject: [PATCH 13/17] Add minor LakPackage support to api extensions --- modflowapi/extensions/advpaks.py | 10 +++++----- modflowapi/extensions/data.py | 1 + modflowapi/extensions/datamodel.py | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/modflowapi/extensions/advpaks.py b/modflowapi/extensions/advpaks.py index 96e9ea8..dddac9a 100644 --- a/modflowapi/extensions/advpaks.py +++ b/modflowapi/extensions/advpaks.py @@ -38,16 +38,16 @@ def diversions(self, recarray): """ if isinstance(recarray, np.recarray): - self._package_vars.values = recarray + self._diversion_vars.values = recarray elif isinstance(recarray, ListInput): - self._package_vars.values = recarray.values + self._diversion_vars.values = recarray.values elif recarray is None: - self._package_vars.values = recarray + self._diversion_vars.values = recarray else: - raise TypeError(f"{type(recarray)} is not a supported stress_period_data type") + raise TypeError(f"{type(recarray)} is not a supported diversions type") -class LakPackage: +class LakPackage(AdvancedPackage): """ Container for LAK and LAK like packages diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index 655e50b..6d190a2 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -118,6 +118,7 @@ def _set_data(self): elif reduced in ( "nexg", "maxats", + "nlakes" ): setattr(self, "_maxbound", values) setattr(self, "_nbound", values) diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index c39f712..5b6969a 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -269,11 +269,21 @@ ("bound", ("ifno:range:maxbound", "finf", "pet", "extdp", "extwc", "ha", "hroot", "rootact")), ], }, + "lak": { + "packagedata": [ + "nlakes", + ( + "ifno:range:nlakes", + "strt", + "nlakeconn" + ) + ], + }, } def get_package_type(pkg_type): - from .advpaks import SfrPakage, UzfPackage + from .advpaks import LakPackage, SfrPakage, UzfPackage from .pakbase import AdvancedPackage, ArrayPackage, ListPackage, ScalarPackage pkg_types = { @@ -291,7 +301,7 @@ def get_package_type(pkg_type): # advanced "sfr": SfrPakage, "uzf": UzfPackage, - # "lak": abc, + "lak": LakPackage, # "maw": None, # "csub": None, # gwt From a637e1c12c54abb5251368a033808a608bb4e246 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Thu, 10 Apr 2025 17:22:13 -0700 Subject: [PATCH 14/17] linting --- modflowapi/extensions/data.py | 6 +----- modflowapi/extensions/datamodel.py | 9 +-------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index 6d190a2..dc163e9 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -115,11 +115,7 @@ def _set_data(self): setattr(self, f"_{reduced}", values) if not self._spd and reduced == "maxbound": self._nbound = values - elif reduced in ( - "nexg", - "maxats", - "nlakes" - ): + elif reduced in ("nexg", "maxats", "nlakes"): setattr(self, "_maxbound", values) setattr(self, "_nbound", values) elif reduced in ("naux",): diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index 5b6969a..87cb64f 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -270,14 +270,7 @@ ], }, "lak": { - "packagedata": [ - "nlakes", - ( - "ifno:range:nlakes", - "strt", - "nlakeconn" - ) - ], + "packagedata": ["nlakes", ("ifno:range:nlakes", "strt", "nlakeconn")], }, } From 73ea85c3ee5e50d972c8625e52e31c437a578272 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Fri, 11 Apr 2025 08:28:08 -0700 Subject: [PATCH 15/17] Add basic support for `MawPackage` --- modflowapi/extensions/advpaks.py | 2 +- modflowapi/extensions/data.py | 2 +- modflowapi/extensions/datamodel.py | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modflowapi/extensions/advpaks.py b/modflowapi/extensions/advpaks.py index dddac9a..aecda8a 100644 --- a/modflowapi/extensions/advpaks.py +++ b/modflowapi/extensions/advpaks.py @@ -67,7 +67,7 @@ def __init__(self, model, pkg_type, pkg_name, sim_package=False): super().__init__(model, pkg_type, pkg_name, sim_package) -class MawPackage: +class MawPackage(AdvancedPackage): """ Container for MAW and MAW like packages diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index dc163e9..e8e450a 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -115,7 +115,7 @@ def _set_data(self): setattr(self, f"_{reduced}", values) if not self._spd and reduced == "maxbound": self._nbound = values - elif reduced in ("nexg", "maxats", "nlakes"): + elif reduced in ("nexg", "maxats", "nlakes", "nmawwells"): setattr(self, "_maxbound", values) setattr(self, "_nbound", values) elif reduced in ("naux",): diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index 87cb64f..669a0d5 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -272,11 +272,12 @@ "lak": { "packagedata": ["nlakes", ("ifno:range:nlakes", "strt", "nlakeconn")], }, + "maw": {"packagedata": ["nmawwells", ("ifno:range:nmawwells", "radius", "bot", "strt", "ngwfnodes")]}, } def get_package_type(pkg_type): - from .advpaks import LakPackage, SfrPakage, UzfPackage + from .advpaks import LakPackage, MawPackage, SfrPakage, UzfPackage from .pakbase import AdvancedPackage, ArrayPackage, ListPackage, ScalarPackage pkg_types = { @@ -295,7 +296,7 @@ def get_package_type(pkg_type): "sfr": SfrPakage, "uzf": UzfPackage, "lak": LakPackage, - # "maw": None, + "maw": MawPackage, # "csub": None, # gwt "dsp": ArrayPackage, From df7810a3880fc1c4f5212f31d9a8fb0f6bb06c77 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Fri, 11 Apr 2025 08:30:14 -0700 Subject: [PATCH 16/17] Cleanup unused class objects --- modflowapi/extensions/advpaks.py | 44 -------------------------------- 1 file changed, 44 deletions(-) diff --git a/modflowapi/extensions/advpaks.py b/modflowapi/extensions/advpaks.py index aecda8a..f0c6ae0 100644 --- a/modflowapi/extensions/advpaks.py +++ b/modflowapi/extensions/advpaks.py @@ -105,47 +105,3 @@ class UzfPackage(AdvancedPackage): def __init__(self, model, pkg_type, pkg_name, sim_package=False): super().__init__(model, pkg_type, pkg_name, sim_package) - - -class CsubPackage: - """ - Container for CSUB packages - - Parameters - ---------- - model : ApiModel - modflowapi model object - pkg_type : str - package type. Ex. "CSUB" - pkg_name : str - package name (in the mf6 variables) - sim_package : bool - boolean flag for simulation level packages. Ex. TDIS, IMS - """ - - def __init__( - self, - ): - self.x = None - - -class MvrPackage: - """ - Container for MVR packages - - Parameters - ---------- - model : ApiModel - modflowapi model object - pkg_type : str - package type. Ex. "MVR" - pkg_name : str - package name (in the mf6 variables) - sim_package : bool - boolean flag for simulation level packages. Ex. TDIS, IMS - """ - - def __init__( - self, - ): - self.x = None From 7cf336cc3ce76f5be2c4658c6ec4e4bebea473b5 Mon Sep 17 00:00:00 2001 From: jlarsen Date: Fri, 11 Apr 2025 09:55:55 -0700 Subject: [PATCH 17/17] Remove notes and update docstrings --- modflowapi/extensions/data.py | 19 +++++++++++++++++-- modflowapi/extensions/pakbase.py | 5 ++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index e8e450a..c104da2 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -276,13 +276,19 @@ def _recarray_to_ptr(self, recarray): def _special_condition_to_values(self, ctype, inval): """ + Method to catch and set special, compound conditions where necessary data + is not directly available from MODFLOW Parameters ---------- - sp_cond + ctype : str + condition type + inval : int, float, np.ndarray + input data value Returns ------- + outval : np.array of values """ functions = { "range": lambda x: np.arange(0, int(x), dtype=int), @@ -300,13 +306,22 @@ def _special_condition_to_values(self, ctype, inval): def _special_condition_to_ptr(self, recarray, name, visited): """ + Method to catch and set special, compound conditions to the associated MODFLOW + ptr where the user data is not directly available from MODFLOW Parameters + ---------- + recarray : np.recarray + recarray of user data + name : str + data column name + visited : list + list of visited data columns used to avoid duplicate processing Returns ------- - + visited : list """ functions = { "range": lambda x: [ diff --git a/modflowapi/extensions/pakbase.py b/modflowapi/extensions/pakbase.py index a274594..2a48c9b 100644 --- a/modflowapi/extensions/pakbase.py +++ b/modflowapi/extensions/pakbase.py @@ -478,7 +478,6 @@ def __init__(self, model, pkg_type, pkg_name, sim_package=False): # create variable addresses!!!! for var in self._adv_var_dict["perioddata"]: if isinstance(var, tuple): - # todo: check if there's a compound variable in bound use_bound = True for v in var[-1]: if ":" in v: @@ -566,6 +565,10 @@ def packagedata(self, recarray): """ Setter method to update the packagedata + Parameters + ---------- + recarray : np.recarray, ListInput, or None + """ if self._package_vars is not None: if isinstance(recarray, np.recarray):