From b1d17d7ad02d1888a45d8d3fa2148c2c6151109e Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Mon, 13 Nov 2023 17:56:12 -0500 Subject: [PATCH 1/4] refactor(datafiles): use 0-based kstp/kper indexing, deprecate get_kstpkper() --- flopy/utils/binaryfile.py | 50 ++++++++++++++++++------------------ flopy/utils/datafile.py | 17 ++++++------ flopy/utils/formattedfile.py | 7 +++-- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/flopy/utils/binaryfile.py b/flopy/utils/binaryfile.py index cf384fa359..349d834c91 100644 --- a/flopy/utils/binaryfile.py +++ b/flopy/utils/binaryfile.py @@ -458,10 +458,12 @@ def _build_index(self): if self.nrow < 0 or self.ncol < 0: raise Exception("negative nrow, ncol") - if self.nrow > 1 and self.nrow * self.ncol > 10000000: - s = "Possible error. ncol ({}) * nrow ({}) > 10,000,000 " - s = s.format(self.ncol, self.nrow) - warnings.warn(s) + + warn_threshold = 10000000 + if self.nrow > 1 and self.nrow * self.ncol > warn_threshold: + warnings.warn( + f"Very large grid, ncol ({self.ncol}) * nrow ({self.nrow}) > {warn_threshold}" + ) self.file.seek(0, 2) self.totalbytes = self.file.tell() self.file.seek(0, 0) @@ -473,14 +475,14 @@ def _build_index(self): continue if ipos == 0: self.times.append(header["totim"]) - kstpkper = (header["kstp"], header["kper"]) - self.kstpkper.append(kstpkper) + self.kstpkper.append((header["kstp"] - 1, header["kper"] - 1)) else: totim = header["totim"] if totim != self.times[-1]: self.times.append(totim) - kstpkper = (header["kstp"], header["kper"]) - self.kstpkper.append(kstpkper) + self.kstpkper.append( + (header["kstp"] - 1, header["kper"] - 1) + ) ipos = self.file.tell() self.iposarray.append(ipos) databytes = self.get_databytes(header) @@ -609,7 +611,7 @@ class HeadFile(BinaryLayerFile): >>> import flopy.utils.binaryfile as bf >>> hdobj = bf.HeadFile('model.hds', precision='single') >>> hdobj.list_records() - >>> rec = hdobj.get_data(kstpkper=(1, 50)) + >>> rec = hdobj.get_data(kstpkper=(0, 50)) >>> ddnobj = bf.HeadFile('model.ddn', text='drawdown', precision='single') >>> ddnobj.list_records() @@ -762,7 +764,7 @@ class UcnFile(BinaryLayerFile): >>> import flopy.utils.binaryfile as bf >>> ucnobj = bf.UcnFile('MT3D001.UCN', precision='single') >>> ucnobj.list_records() - >>> rec = ucnobj.get_data(kstpkper=(1,1)) + >>> rec = ucnobj.get_data(kstpkper=(0, 0)) """ @@ -1200,7 +1202,7 @@ def _build_index(self): header["totim"] = totim if totim >= 0 and totim not in self.times: self.times.append(totim) - kstpkper = (header["kstp"], header["kper"]) + kstpkper = (header["kstp"] - 1, header["kper"] - 1) if kstpkper not in self.kstpkper: self.kstpkper.append(kstpkper) if header["text"] not in self.textlist: @@ -1505,19 +1507,18 @@ def _unique_package_names(self): def get_kstpkper(self): """ - Get a list of unique stress periods and time steps in the file + Get stress period and time step tuples. Returns - ---------- - out : list of (kstp, kper) tuples - List of unique kstp, kper combinations in binary file. kstp and - kper values are zero-based. + ------- + list of (kstp, kper) tuples + List of unique combinations of stress period & + time step indices (0-based) in the binary file + .. deprecated:: 3.5 + Use kstpkper property instead """ - kstpkper = [] - for kstp, kper in self.kstpkper: - kstpkper.append((kstp - 1, kper - 1)) - return kstpkper + return self.kstpkper def get_indices(self, text=None): """ @@ -1764,25 +1765,24 @@ def get_ts(self, idx, text=None, times=None): # Initialize result array and put times in first column result = self._init_result(nstation) - kk = self.get_kstpkper() timesint = self.get_times() if len(timesint) < 1: if times is None: - timesint = [x + 1 for x in range(len(kk))] + timesint = [x + 1 for x in range(len(self.kstpkper))] else: if isinstance(times, np.ndarray): times = times.tolist() - if len(times) != len(kk): + if len(times) != len(self.kstpkper): raise Exception( "times passed to CellBudgetFile get_ts() " "method must be equal to {} " - "not {}".format(len(kk), len(times)) + "not {}".format(len(self.kstpkper), len(times)) ) timesint = times for idx, t in enumerate(timesint): result[idx, 0] = t - for itim, k in enumerate(kk): + for itim, k in enumerate(self.kstpkper): try: v = self.get_data(kstpkper=k, text=text, full3D=True) # skip missing data - required for storage diff --git a/flopy/utils/datafile.py b/flopy/utils/datafile.py index 81b84901cc..cb30bbe758 100644 --- a/flopy/utils/datafile.py +++ b/flopy/utils/datafile.py @@ -477,19 +477,18 @@ def get_times(self): def get_kstpkper(self): """ - Get a list of unique stress periods and time steps in the file + Get stress period and time step tuples. Returns - ---------- - out : list of (kstp, kper) tuples - List of unique kstp, kper combinations in binary file. kstp and - kper values are presently zero-based. + ------- + list of (kstp, kper) tuples + List of unique combinations of stress period & + time step indices (0-based) in the binary file + .. deprecated:: 3.5 + Use kstpkper property instead """ - kstpkper = [] - for kstp, kper in self.kstpkper: - kstpkper.append((kstp - 1, kper - 1)) - return kstpkper + return self.kstpkper def get_data(self, kstpkper=None, idx=None, totim=None, mflay=None): """ diff --git a/flopy/utils/formattedfile.py b/flopy/utils/formattedfile.py index db53aa9cfa..dd2f692ff4 100644 --- a/flopy/utils/formattedfile.py +++ b/flopy/utils/formattedfile.py @@ -117,7 +117,7 @@ def _build_index(self): Build the recordarray and iposarray, which maps the header information to the position in the formatted file. """ - self.kstpkper # array of time step/stress periods with data available + self.kstpkper # 0-based array of time step/stress periods self.recordarray # array of data headers self.iposarray # array of seek positions for each record self.nlay # Number of model layers @@ -167,7 +167,7 @@ def _store_record(self, header, ipos): totim = header["totim"] if totim > 0 and totim not in self.times: self.times.append(totim) - kstpkper = (header["kstp"], header["kper"]) + kstpkper = (header["kstp"] - 1, header["kper"] - 1) if kstpkper not in self.kstpkper: self.kstpkper.append(kstpkper) @@ -356,10 +356,9 @@ class FormattedHeadFile(FormattedLayerFile): >>> import flopy.utils.formattedfile as ff >>> hdobj = ff.FormattedHeadFile('model.fhd', precision='single') >>> hdobj.list_records() - >>> rec = hdobj.get_data(kstpkper=(1, 50)) + >>> rec = hdobj.get_data(kstpkper=(0, 50)) >>> rec2 = ddnobj.get_data(totim=100.) - """ def __init__( From 485cbfb370619fbb233df0de96b220740f353724 Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Tue, 21 Nov 2023 21:04:05 -0500 Subject: [PATCH 2/4] don't deprecate get_kstpkper() --- flopy/utils/binaryfile.py | 3 --- flopy/utils/datafile.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/flopy/utils/binaryfile.py b/flopy/utils/binaryfile.py index 349d834c91..d66cc522ec 100644 --- a/flopy/utils/binaryfile.py +++ b/flopy/utils/binaryfile.py @@ -1514,9 +1514,6 @@ def get_kstpkper(self): list of (kstp, kper) tuples List of unique combinations of stress period & time step indices (0-based) in the binary file - - .. deprecated:: 3.5 - Use kstpkper property instead """ return self.kstpkper diff --git a/flopy/utils/datafile.py b/flopy/utils/datafile.py index cb30bbe758..60469b9ec8 100644 --- a/flopy/utils/datafile.py +++ b/flopy/utils/datafile.py @@ -484,9 +484,6 @@ def get_kstpkper(self): list of (kstp, kper) tuples List of unique combinations of stress period & time step indices (0-based) in the binary file - - .. deprecated:: 3.5 - Use kstpkper property instead """ return self.kstpkper From 9585f9db762351ac08c5d81a76fbf3b4d3414bd9 Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Wed, 22 Nov 2023 16:03:14 -0500 Subject: [PATCH 3/4] restore kstpkper to 1-based, fix usage examples --- flopy/utils/binaryfile.py | 30 +++++++++++++++--------------- flopy/utils/datafile.py | 5 +++-- flopy/utils/formattedfile.py | 8 +++----- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/flopy/utils/binaryfile.py b/flopy/utils/binaryfile.py index d66cc522ec..1f9a2c9099 100644 --- a/flopy/utils/binaryfile.py +++ b/flopy/utils/binaryfile.py @@ -475,14 +475,12 @@ def _build_index(self): continue if ipos == 0: self.times.append(header["totim"]) - self.kstpkper.append((header["kstp"] - 1, header["kper"] - 1)) + self.kstpkper.append((header["kstp"], header["kper"])) else: totim = header["totim"] if totim != self.times[-1]: self.times.append(totim) - self.kstpkper.append( - (header["kstp"] - 1, header["kper"] - 1) - ) + self.kstpkper.append((header["kstp"], header["kper"])) ipos = self.file.tell() self.iposarray.append(ipos) databytes = self.get_databytes(header) @@ -611,7 +609,7 @@ class HeadFile(BinaryLayerFile): >>> import flopy.utils.binaryfile as bf >>> hdobj = bf.HeadFile('model.hds', precision='single') >>> hdobj.list_records() - >>> rec = hdobj.get_data(kstpkper=(0, 50)) + >>> rec = hdobj.get_data(kstpkper=(0, 49)) >>> ddnobj = bf.HeadFile('model.ddn', text='drawdown', precision='single') >>> ddnobj.list_records() @@ -1202,7 +1200,7 @@ def _build_index(self): header["totim"] = totim if totim >= 0 and totim not in self.times: self.times.append(totim) - kstpkper = (header["kstp"] - 1, header["kper"] - 1) + kstpkper = (header["kstp"], header["kper"]) if kstpkper not in self.kstpkper: self.kstpkper.append(kstpkper) if header["text"] not in self.textlist: @@ -1507,7 +1505,8 @@ def _unique_package_names(self): def get_kstpkper(self): """ - Get stress period and time step tuples. + Get a list of unique tuples (stress period, time step) in the file. + Indices are 0-based, use the `kstpkper` attribute for 1-based. Returns ------- @@ -1515,7 +1514,7 @@ def get_kstpkper(self): List of unique combinations of stress period & time step indices (0-based) in the binary file """ - return self.kstpkper + return [(kstp - 1, kper - 1) for kstp, kper in self.kstpkper] def get_indices(self, text=None): """ @@ -1763,23 +1762,24 @@ def get_ts(self, idx, text=None, times=None): result = self._init_result(nstation) timesint = self.get_times() + kstpkper = self.get_kstpkper() + nsteps = len(kstpkper) if len(timesint) < 1: if times is None: - timesint = [x + 1 for x in range(len(self.kstpkper))] + timesint = [x + 1 for x in range(nsteps)] else: if isinstance(times, np.ndarray): times = times.tolist() - if len(times) != len(self.kstpkper): - raise Exception( - "times passed to CellBudgetFile get_ts() " - "method must be equal to {} " - "not {}".format(len(self.kstpkper), len(times)) + if len(times) != nsteps: + raise ValueError( + f"number of times provided ({len(times)}) must equal " + f"number of time steps in cell budget file ({nsteps})" ) timesint = times for idx, t in enumerate(timesint): result[idx, 0] = t - for itim, k in enumerate(self.kstpkper): + for itim, k in enumerate(kstpkper): try: v = self.get_data(kstpkper=k, text=text, full3D=True) # skip missing data - required for storage diff --git a/flopy/utils/datafile.py b/flopy/utils/datafile.py index 60469b9ec8..122b7d5bba 100644 --- a/flopy/utils/datafile.py +++ b/flopy/utils/datafile.py @@ -477,7 +477,8 @@ def get_times(self): def get_kstpkper(self): """ - Get stress period and time step tuples. + Get a list of unique tuples (stress period, time step) in the file. + Indices are 0-based, use `kstpkper` attribute for 1-based. Returns ------- @@ -485,7 +486,7 @@ def get_kstpkper(self): List of unique combinations of stress period & time step indices (0-based) in the binary file """ - return self.kstpkper + return [(kstp - 1, kper - 1) for kstp, kper in self.kstpkper] def get_data(self, kstpkper=None, idx=None, totim=None, mflay=None): """ diff --git a/flopy/utils/formattedfile.py b/flopy/utils/formattedfile.py index dd2f692ff4..dd4c795bae 100644 --- a/flopy/utils/formattedfile.py +++ b/flopy/utils/formattedfile.py @@ -117,7 +117,7 @@ def _build_index(self): Build the recordarray and iposarray, which maps the header information to the position in the formatted file. """ - self.kstpkper # 0-based array of time step/stress periods + self.kstpkper # 1-based array of time step/stress periods self.recordarray # array of data headers self.iposarray # array of seek positions for each record self.nlay # Number of model layers @@ -155,7 +155,6 @@ def _build_index(self): self.recordarray = np.array(self.recordarray, self.header.get_dtype()) self.iposarray = np.array(self.iposarray) self.nlay = np.max(self.recordarray["ilay"]) - return def _store_record(self, header, ipos): """ @@ -167,7 +166,7 @@ def _store_record(self, header, ipos): totim = header["totim"] if totim > 0 and totim not in self.times: self.times.append(totim) - kstpkper = (header["kstp"] - 1, header["kper"] - 1) + kstpkper = (header["kstp"], header["kper"]) if kstpkper not in self.kstpkper: self.kstpkper.append(kstpkper) @@ -356,7 +355,7 @@ class FormattedHeadFile(FormattedLayerFile): >>> import flopy.utils.formattedfile as ff >>> hdobj = ff.FormattedHeadFile('model.fhd', precision='single') >>> hdobj.list_records() - >>> rec = hdobj.get_data(kstpkper=(0, 50)) + >>> rec = hdobj.get_data(kstpkper=(0, 49)) >>> rec2 = ddnobj.get_data(totim=100.) """ @@ -371,7 +370,6 @@ def __init__( ): self.text = text super().__init__(filename, precision, verbose, kwargs) - return def _get_text_header(self): """ From 17f2270aeaf01f57cc96d78e6639edd672939894 Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Wed, 22 Nov 2023 16:19:41 -0500 Subject: [PATCH 4/4] more cleanup, update more usage examples --- flopy/utils/binaryfile.py | 2 +- flopy/utils/datafile.py | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/flopy/utils/binaryfile.py b/flopy/utils/binaryfile.py index 1f9a2c9099..8ac1416ae4 100644 --- a/flopy/utils/binaryfile.py +++ b/flopy/utils/binaryfile.py @@ -829,7 +829,7 @@ class HeadUFile(BinaryLayerFile): >>> import flopy.utils.binaryfile as bf >>> hdobj = bf.HeadUFile('model.hds') >>> hdobj.list_records() - >>> usgheads = hdobj.get_data(kstpkper=(1, 50)) + >>> usgheads = hdobj.get_data(kstpkper=(0, 49)) """ diff --git a/flopy/utils/datafile.py b/flopy/utils/datafile.py index 122b7d5bba..5c5d3dc811 100644 --- a/flopy/utils/datafile.py +++ b/flopy/utils/datafile.py @@ -149,9 +149,8 @@ def get_values(self): class LayerFile: """ - The LayerFile class is the abstract base class from which specific derived - classes are formed. LayerFile This class should not be instantiated - directly. + Base class for layered output files. + Do not instantiate directly. """ @@ -478,7 +477,7 @@ def get_times(self): def get_kstpkper(self): """ Get a list of unique tuples (stress period, time step) in the file. - Indices are 0-based, use `kstpkper` attribute for 1-based. + Indices are 0-based, use the `kstpkper` attribute for 1-based. Returns ------- @@ -495,10 +494,9 @@ def get_data(self, kstpkper=None, idx=None, totim=None, mflay=None): Parameters ---------- idx : int - The zero-based record number. The first record is record 0. + The zero-based record number. The first record is record 0. kstpkper : tuple of ints - A tuple containing the time step and stress period (kstp, kper). - These are zero-based kstp and kper values. + A tuple (kstep, kper) of zero-based time step and stress period. totim : float The simulation time. mflay : integer @@ -511,14 +509,9 @@ def get_data(self, kstpkper=None, idx=None, totim=None, mflay=None): Array has size (nlay, nrow, ncol) if mflay is None or it has size (nrow, ncol) if mlay is specified. - See Also - -------- - Notes ----- - if both kstpkper and totim are None, will return the last entry - Examples - -------- + If both kstpkper and totim are None, the last entry will be returned. """ # One-based kstp and kper for pulling out of recarray