diff --git a/autotest/t027_test.py b/autotest/t027_test.py index 6c0153a905..cd035b91bc 100644 --- a/autotest/t027_test.py +++ b/autotest/t027_test.py @@ -68,7 +68,8 @@ def test_mnw1_load_write(): m2 = flopy.modflow.Modflow.load('mnw1.nam', model_ws=cpth, load_only=['mnw1'], verbose=True, forgive=False) - assert m.stress_period_data == m2.stress_period_data + for k, v in m.mnw1.stress_period_data.data.items(): + assert np.array_equal(v, m2.mnw1.stress_period_data[k]) def test_make_package(): """t027 test make MNW2 Package""" diff --git a/autotest/t066_test_copy.py b/autotest/t066_test_copy.py new file mode 100644 index 0000000000..bf07ccf9e2 --- /dev/null +++ b/autotest/t066_test_copy.py @@ -0,0 +1,208 @@ +"""Test copying of flopy objects. +""" +import sys +import os +import copy +import inspect +import numpy as np +import flopy +fm = flopy.modflow +mf6 = flopy.mf6 +from flopy.datbase import DataType, DataInterface +from flopy.mbase import ModelInterface +from flopy.utils import TemporalReference + + +def get_package_list(model): + if model.version == 'mf6': + packages = [p.name[0].upper() for p in model.packagelist] + else: + packages = model.get_package_list() + return packages + + +def model_is_copy(m1, m2): + """Test that m2 is a copy of m1 by + checking for different identities in their attributes + and equality between their attributes. + """ + if m1 is m2: + return False + m1packages = get_package_list(m1) + m2packages = get_package_list(m2) + if m1packages is m2packages: + return False + if m2.modelgrid != m1.modelgrid: + if not package_is_copy(m1.modelgrid, m2.modelgrid): + return False + for k, v in m1.__dict__.items(): + v2 = m2.__dict__[k] + if v2 is v and type(v) not in [bool, str, type(None), float, int]: + # some mf6 objects aren't copied with deepcopy + if isinstance(v, mf6.mfsimulation.MFSimulationData): + continue + elif isinstance(v, list): + for i, item in enumerate(v): + item2 = v2[i] + if item is item2: + return False + else: + return False + if k in [ + '_packagelist', + '_package_paths', + 'package_key_dict', + 'package_type_dict', + 'package_name_dict', + '_ftype_num_dict']: + continue + elif k not in m2.__dict__: + return False + elif type(v) == bool: + if not v == v2: + return False + elif type(v) in [str, int, float, dict, list]: + if v != v2: + return False + continue + for pk in m1packages: + if not package_is_copy(getattr(m1, pk), getattr(m2, pk)): + return False + return True + + +def package_is_copy(pk1, pk2): + """Test that pk2 is a copy of pk1 by + checking for different identities in their attributes + and equality between their attributes. + """ + for k, v in pk1.__dict__.items(): + v2 = pk2.__dict__[k] + if v2 is v and type(v) not in [bool, str, type(None), + float, int, tuple + ]: + # Deep copy doesn't work for ModflowUtltas + if not inspect.isclass(v): + return False + if k in ['_child_package_groups', + '_data_list', + '_packagelist', + '_simulation_data', + 'blocks', + 'dimensions', + 'package_key_dict', + 'package_name_dict', + 'package_type_dict', + 'post_block_comments', + 'simulation_data', + 'structure' + ]: + continue + elif isinstance(v, mf6.mfpackage.MFPackage): + continue + elif isinstance(v, mf6.mfpackage.MFChildPackages): + if not package_is_copy(v, v2): + return False + elif k not in pk2.__dict__: + return False + elif type(v) == bool: + if not v == v2: + return False + elif type(v) in [str, int, float, dict, list]: + if v != v2: + return False + elif isinstance(v, ModelInterface): + # weak, but calling model_eq would result in recursion + if v.__repr__() != v2.__repr__(): + return False + elif isinstance(v, DataInterface): + if v != v2: + if v.data_type == DataType.transientlist or \ + v.data_type == DataType.list: + if not list_is_copy(v, v2): + return False + else: + a1, a2 = v.array, v2.array + if a2 is a1 and type(a1) not in [bool, str, type(None), float, int, tuple]: + return False + if a1 is None and a2 is None: + continue + if not isinstance(a1, np.ndarray): + if a1 != a2: + return False + # TODO: this may return False if there are nans + elif not np.allclose(v.array, v2.array): + return False + elif isinstance(v, TemporalReference): + pass + elif isinstance(v, np.ndarray): + if not np.allclose(v, v2): + return False + elif v != v2: + return False + return True + + +def list_is_copy(mflist1, mflist2): + """Test that mflist2 is a copy of mflist1 by + checking that their arrays have different identities + but are equal or close. + """ + if mflist2 is mflist1: + return False + if isinstance(mflist1, mf6.data.mfdatalist.MFTransientList): + data1 = {per: ra for per, ra in enumerate(mflist1.array)} + data2 = {per: ra for per, ra in enumerate(mflist2.array)} + elif isinstance(mflist1, mf6.data.mfdatalist.MFList): + data1 = {0: mflist1.array} + data2 = {0: mflist2.array} + elif hasattr(mflist1, 'data'): + data1 = mflist1.data + data2 = mflist2.data + for k, v in data1.items(): + if k not in data2: + return False + v2 = data2[k] + if v2 is v and type(v) not in [bool, str, type(None), float, int, tuple]: + return False + if v is None and v2 is None: + continue + elif not isinstance(v, np.recarray): + if v != v2: + return False + else: + # compare the two np.recarrays + # not sure if this will work for all relevant cases + for c, dtype in v.dtype.fields.items(): + c1 = v[c].copy() + c2 = v2[c].copy() + if np.issubdtype(dtype[0].type, np.floating): + c1[np.isnan(c1)] = 0 + c2[np.isnan(c2)] = 0 + if not np.array_equal(c1, c2): + return False + return True + + +def test_mf2005_copy(): + if sys.version_info[0] < 3: + return + path = '../examples/data/freyberg_multilayer_transient/freyberg.nam' + model_ws, namefile = os.path.split(path) + m = fm.Modflow.load(namefile, model_ws=model_ws) + m_c = copy.copy(m) + m_dc = copy.deepcopy(m) + assert model_is_copy(m, m_dc) + assert not model_is_copy(m, m_c) + + +def test_mf6_copy(): + if sys.version_info[0] < 3: + return + sim_ws = '../examples/data/mf6/test045_lake2tr' + sim = mf6.MFSimulation.load('mfsim.nam', 'mf6', sim_ws=sim_ws) + m = sim.get_model('lakeex2a') + m_c = copy.copy(m) + m_dc = copy.deepcopy(m) + assert model_is_copy(m, m_dc) + assert not model_is_copy(m, m_c) diff --git a/flopy/export/utils.py b/flopy/export/utils.py index 3266b3cf69..b1afc94b97 100644 --- a/flopy/export/utils.py +++ b/flopy/export/utils.py @@ -293,8 +293,8 @@ def output_helper(f, ml, oudic, **kwargs): if ml.bas6: mask_vals.append(ml.bas6.hnoflo) mask_array3d = ml.bas6.ibound.array == 0 - if ml.bcf: - mask_vals.append(ml.bcf.hdry) + if ml.bcf6: + mask_vals.append(ml.bcf6.hdry) if ml.lpf: mask_vals.append(ml.lpf.hdry) diff --git a/flopy/mbase.py b/flopy/mbase.py index aa87503678..122b15a904 100644 --- a/flopy/mbase.py +++ b/flopy/mbase.py @@ -203,6 +203,7 @@ def __init__(self, modelname='modflowtest', namefile_ext='nam', self.heading = '' self.exe_name = exe_name self._verbose = verbose + self.external_path = None self.external_extension = 'ref' if model_ws is None: model_ws = os.getcwd() if not os.path.exists(model_ws): @@ -526,8 +527,16 @@ def __getattr__(self, item): return self.dis.start_datetime else: return None - - return self.get_package(item) + #return self.get_package(item) + # to avoid infinite recursion + if item == "_packagelist" or item == "packagelist": + raise AttributeError(item) + pckg = self.get_package(item) + if pckg is not None or item in self.mfnam_packages: + return pckg + if item == 'modelgrid': + return + raise AttributeError(item) def get_ext_dict_attr(self, ext_unit_dict=None, unit=None, filetype=None, pop_key=True): diff --git a/flopy/modflow/mf.py b/flopy/modflow/mf.py index 31ea48c9dc..cc609eb5d0 100644 --- a/flopy/modflow/mf.py +++ b/flopy/modflow/mf.py @@ -256,7 +256,7 @@ def modelgrid(self): if not self._mg_resync: return self._modelgrid - if self.bas6 is not None: + if self.has_package('bas6'): ibound = self.bas6.ibound.array else: ibound = None diff --git a/flopy/modflow/mfsfr2.py b/flopy/modflow/mfsfr2.py index 8e1baab0af..cab6be1e2e 100644 --- a/flopy/modflow/mfsfr2.py +++ b/flopy/modflow/mfsfr2.py @@ -1967,6 +1967,7 @@ def __init__(self, sfrpackage, verbose=True, level=1): self.sr = self.sfr.parent.modelgrid.sr except AttributeError: self.sr = self.sfr.parent.sr + self.mg = None self.reach_data = sfrpackage.reach_data self.segment_data = sfrpackage.segment_data diff --git a/flopy/mt3d/mt.py b/flopy/mt3d/mt.py index d1ec181b58..c19de1dcb8 100644 --- a/flopy/mt3d/mt.py +++ b/flopy/mt3d/mt.py @@ -243,6 +243,7 @@ def __init__(self, modelname='mt3dtest', namefile_ext='nam', self.ftlfilename = ftlfilename self.ftlfree = ftlfree self.ftlunit = ftlunit + self.free_format = None # Check whether specified ftlfile exists in model directory; if not, # warn user diff --git a/flopy/seawat/swt.py b/flopy/seawat/swt.py index df2173ff46..34535c360c 100644 --- a/flopy/seawat/swt.py +++ b/flopy/seawat/swt.py @@ -90,6 +90,9 @@ def __init__(self, modelname='swttest', namefile_ext='nam', self.version_types = {'seawat': 'SEAWAT'} self.set_version(version) self.lst = SeawatList(self, listunit=listunit) + self.glo = None + self._mf = None + self._mt = None # If a MODFLOW model was passed in, then add its packages self.mf = self @@ -160,7 +163,7 @@ def modelgrid(self): if not self._mg_resync: return self._modelgrid - if self.bas6 is not None: + if self.has_package('bas6'): ibound = self.bas6.ibound.array else: ibound = None diff --git a/flopy/utils/check.py b/flopy/utils/check.py index 2d655bf0b5..24ccf5034a 100644 --- a/flopy/utils/check.py +++ b/flopy/utils/check.py @@ -82,7 +82,7 @@ def __init__(self, package, f=None, verbose=True, level=1, # allow for instantiation with model or package # if isinstance(package, BaseModel): didn't work - if package.parent is not None: + if hasattr(package, 'parent'): self.model = package.parent self.prefix = '{} PACKAGE DATA VALIDATION'.format(package.name[0]) else: diff --git a/flopy/utils/util_list.py b/flopy/utils/util_list.py index c9ece34108..4de979cceb 100644 --- a/flopy/utils/util_list.py +++ b/flopy/utils/util_list.py @@ -251,7 +251,7 @@ def fmt_string(self): use_free = self.list_free_format else: use_free = True - if self.package.parent.bas6 is not None: + if self.package.parent.has_package('bas6'): use_free = self.package.parent.bas6.ifrefm # mt3d list data is fixed format if 'mt3d' in self.package.parent.version.lower():