Skip to content

Commit 2f1d78e

Browse files
committed
refactor(datafile): ignore "text" parameter, add attributes from file
1 parent e2a85a3 commit 2f1d78e

File tree

7 files changed

+142
-55
lines changed

7 files changed

+142
-55
lines changed

autotest/test_binaryfile.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
See also test_cellbudgetfile.py for similar tests.
44
"""
55

6+
import warnings
67
from itertools import repeat
78

89
import numpy as np
@@ -104,6 +105,8 @@ def test_headfile_build_index(example_data_path):
104105
assert hds.ncol == 20
105106
assert hds.nlay == 3
106107
assert not hasattr(hds, "nper")
108+
assert hds.text == "head"
109+
assert hds.text_bytes == b"HEAD".rjust(16)
107110
assert hds.totalbytes == 10_676_004
108111
assert len(hds.recordarray) == 3291
109112
assert type(hds.recordarray) == np.ndarray
@@ -150,7 +153,80 @@ def test_headfile_build_index(example_data_path):
150153
)
151154

152155

153-
def test_concentration_build_index(example_data_path):
156+
@pytest.mark.parametrize(
157+
"pth, expected",
158+
[
159+
pytest.param(
160+
"mf6-freyberg/freyberg.hds",
161+
{
162+
"precision": "double",
163+
"nlay, nrow, ncol": (1, 40, 20),
164+
"text": "head",
165+
"text_bytes": b"HEAD".ljust(16),
166+
"len(obj)": 1,
167+
},
168+
id="freyberg.hds",
169+
),
170+
pytest.param(
171+
"mf6/create_tests/test_transport/expected_output/gwt_mst03.ucn",
172+
{
173+
"precision": "double",
174+
"nlay, nrow, ncol": (1, 1, 1),
175+
"text": "concentration",
176+
"text_bytes": b"CONCENTRATION".ljust(16),
177+
"len(obj)": 28,
178+
},
179+
id="gwt_mst03.ucn",
180+
),
181+
pytest.param(
182+
"mfusg_test/03A_conduit_unconfined/output/ex3A.cln.hds",
183+
{
184+
"precision": "single",
185+
"nlay, nrow, ncol": (1, 1, 2),
186+
"text": "cln_heads",
187+
"text_bytes": b"CLN HEADS".rjust(16),
188+
"len(obj)": 1,
189+
},
190+
id="ex3A.cln.hds",
191+
),
192+
pytest.param(
193+
"mfusg_test/03A_conduit_unconfined/output/ex3A.ddn",
194+
{
195+
"precision": "single",
196+
"nlay, nrow, ncol": (2, 100, 100),
197+
"text": "drawdown",
198+
"text_bytes": b"DRAWDOWN".rjust(16),
199+
"len(obj)": 2,
200+
},
201+
id="ex3A.ddn",
202+
),
203+
],
204+
)
205+
def test_headfile_examples(example_data_path, pth, expected):
206+
with HeadFile(example_data_path / pth) as obj:
207+
assert obj.precision == expected["precision"]
208+
assert (obj.nlay, obj.nrow, obj.ncol) == expected["nlay, nrow, ncol"]
209+
assert obj.text == expected["text"]
210+
assert obj.text_bytes == expected["text_bytes"]
211+
assert len(obj) == expected["len(obj)"]
212+
213+
214+
@pytest.mark.parametrize(
215+
"pth",
216+
[
217+
"mt3d_test/mf96mt3d/P01/case1b/MT3D001.UCN",
218+
"unstructured/headu.githds",
219+
],
220+
)
221+
def test_not_headfile(example_data_path, pth):
222+
# These examples pass get_headfile_precision, but are not HeadFiles
223+
with pytest.raises(ValueError, match="cannot read file with HeadFile"):
224+
with warnings.catch_warnings():
225+
warnings.simplefilter("ignore")
226+
HeadFile(example_data_path / pth)
227+
228+
229+
def test_ucnfile_build_index(example_data_path):
154230
# test low-level BinaryLayerFile._build_index() method with UCN file
155231
pth = example_data_path / "mt3d_test/mf2005mt3d/P07/MT3D001.UCN"
156232
with UcnFile(pth) as ucn:
@@ -159,6 +235,8 @@ def test_concentration_build_index(example_data_path):
159235
assert ucn.ncol == 21
160236
assert ucn.nlay == 8
161237
assert not hasattr(ucn, "nper")
238+
assert ucn.text == "concentration"
239+
assert ucn.text_bytes == b"CONCENTRATION".ljust(16)
162240
assert ucn.totalbytes == 10_432
163241
assert len(ucn.recordarray) == 8
164242
assert type(ucn.recordarray) == np.ndarray
@@ -296,6 +374,8 @@ def test_headu_file_data(function_tmpdir, example_data_path):
296374
headobj = HeadUFile(fname)
297375
assert isinstance(headobj, HeadUFile)
298376
assert headobj.nlay == 3
377+
assert headobj.text == "headu"
378+
assert headobj.text_bytes == b"HEADU".rjust(16)
299379

300380
# ensure recordarray is has correct data
301381
ra = headobj.recordarray

autotest/test_formattedfile.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def test_headfile_build_index(example_data_path):
2121
assert hds.ncol == 10
2222
assert hds.nlay == 1
2323
assert not hasattr(hds, "nper")
24+
assert hds.text == "head"
2425
assert hds.totalbytes == 1613
2526
assert len(hds.recordarray) == 1
2627
assert type(hds.recordarray) == np.ndarray

flopy/export/utils.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def _add_output_nc_variable(
179179
else:
180180
a = out_obj.get_data(totim=t)
181181
except Exception as e:
182-
nme = var_name + text.decode().strip().lower()
182+
nme = var_name + text
183183
estr = f"error getting data for {nme} at time {t}:{e!s}"
184184
if logger:
185185
logger.warn(estr)
@@ -191,7 +191,7 @@ def _add_output_nc_variable(
191191
try:
192192
array[i, :, :, :] = a.astype(np.float32)
193193
except Exception as e:
194-
nme = var_name + text.decode().strip().lower()
194+
nme = var_name + text
195195
estr = f"error assigning {nme} data to array for time {t}:{e!s}"
196196
if logger:
197197
logger.warn(estr)
@@ -209,7 +209,7 @@ def _add_output_nc_variable(
209209

210210
if isinstance(nc, dict):
211211
if text:
212-
var_name = text.decode().strip().lower()
212+
var_name = text
213213
nc[var_name] = array
214214
return nc
215215

@@ -219,7 +219,7 @@ def _add_output_nc_variable(
219219
precision_str = "f4"
220220

221221
if text:
222-
var_name = text.decode().strip().lower()
222+
var_name = text
223223
attribs = {"long_name": var_name}
224224
attribs["coordinates"] = "time layer latitude longitude"
225225
attribs["min"] = mn
@@ -434,7 +434,7 @@ def output_helper(
434434
times,
435435
shape3d,
436436
out_obj,
437-
"concentration",
437+
out_obj.text,
438438
logger=logger,
439439
mask_vals=mask_vals,
440440
mask_array3d=mask_array3d,
@@ -446,7 +446,7 @@ def output_helper(
446446
times,
447447
shape3d,
448448
out_obj,
449-
out_obj.text.decode(),
449+
out_obj.text,
450450
logger=logger,
451451
mask_vals=mask_vals,
452452
mask_array3d=mask_array3d,

flopy/export/vtk.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,14 +1228,12 @@ def add_heads(self, hds, kstpkper=None, masked_values=None):
12281228
kstpkpers = hds.get_kstpkper()
12291229
self._totim = {ki: time for (ki, time) in zip(kstpkpers, times)}
12301230

1231-
text = hds.text.decode()
1232-
12331231
d = dict()
12341232
for ki in kstpkper:
12351233
d[ki] = hds.get_data(ki)
12361234

12371235
self.__transient_output_data = False
1238-
self.add_transient_array(d, name=text, masked_values=masked_values)
1236+
self.add_transient_array(d, name=hds.text, masked_values=masked_values)
12391237
self.__transient_output_data = True
12401238

12411239
def add_cell_budget(

flopy/mf6/utils/binaryfile_utils.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def _get_binary_file_object(self, path, bintype, key):
192192

193193
elif bintype == "DDN":
194194
try:
195-
return bf.HeadFile(path, text="drawdown", precision="double")
195+
return bf.HeadFile(path, precision="double")
196196
except AssertionError:
197197
raise AssertionError(f"{self.dataDict[key]} does not exist")
198198

@@ -333,9 +333,7 @@ def _setbinarykeys(self, binarypathdict):
333333

334334
elif key[1] == "DDN":
335335
try:
336-
readddn = bf.HeadFile(
337-
path, text="drawdown", precision="double"
338-
)
336+
readddn = bf.HeadFile(path, precision="double")
339337
self.dataDict[(key[0], key[1], "DRAWDOWN")] = path
340338
readddn.close()
341339

flopy/utils/binaryfile.py

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,10 @@ def _build_index(self):
470470
header = self._get_header()
471471
self.nrow = header["nrow"]
472472
self.ncol = header["ncol"]
473+
self.text_bytes = header["text"]
474+
self.text = (
475+
self.text_bytes.decode("ascii").strip().lower().replace(" ", "_")
476+
)
473477
if header["ilay"] > self.nlay:
474478
self.nlay = header["ilay"]
475479

@@ -488,8 +492,12 @@ def _build_index(self):
488492
while ipos < self.totalbytes:
489493
header = self._get_header()
490494
self.recordarray.append(header)
491-
if self.text.upper() not in header["text"]:
492-
continue
495+
if header["text"] != self.text_bytes:
496+
warnings.warn(
497+
"inconsistent text headers changing from "
498+
f"{self.text_bytes!r} to {header['text']!r}",
499+
UserWarning,
500+
)
493501
if ipos == 0:
494502
self.times.append(header["totim"])
495503
self.kstpkper.append((header["kstp"], header["kper"]))
@@ -501,6 +509,8 @@ def _build_index(self):
501509
ipos = self.file.tell()
502510
self.iposarray.append(ipos)
503511
databytes = self.get_databytes(header)
512+
if ipos + databytes > self.totalbytes:
513+
raise EOFError(f"attempting to seek {ipos + databytes}")
504514
self.file.seek(databytes, 1)
505515
ipos = self.file.tell()
506516

@@ -617,14 +627,13 @@ class HeadFile(BinaryLayerFile):
617627
----------
618628
filename : str or PathLike
619629
Path of the head file.
620-
text : string
621-
Name of the text string in the head file. Default is 'head'.
622-
precision : string
623-
Precision of floating point head data in the value. Accepted
624-
values are 'auto', 'single' or 'double'. Default is 'auto',
625-
which enables automatic detection of precision.
626-
verbose : bool
627-
Toggle logging output. Default is False.
630+
text : str
631+
Ignored.
632+
precision : {'auto', 'single', 'double'}
633+
Precision of floating point head data in the value. Default
634+
'auto' enables automatic detection of precision.
635+
verbose : bool, default False
636+
Toggle logging output.
628637
629638
Examples
630639
--------
@@ -634,7 +643,7 @@ class HeadFile(BinaryLayerFile):
634643
>>> hdobj.list_records()
635644
>>> rec = hdobj.get_data(kstpkper=(0, 49))
636645
637-
>>> ddnobj = bf.HeadFile('model.ddn', text='drawdown', precision='single')
646+
>>> ddnobj = bf.HeadFile('model.ddn', precision='single')
638647
>>> ddnobj.list_records()
639648
>>> rec = ddnobj.get_data(totim=100.)
640649
@@ -643,12 +652,11 @@ class HeadFile(BinaryLayerFile):
643652
def __init__(
644653
self,
645654
filename: Union[str, os.PathLike],
646-
text="head",
655+
text="head", # noqa ARG002
647656
precision="auto",
648657
verbose=False,
649658
**kwargs,
650659
):
651-
self.text = text.encode()
652660
if precision == "auto":
653661
precision = get_headfile_precision(filename)
654662
if precision == "unknown":
@@ -749,14 +757,15 @@ class UcnFile(BinaryLayerFile):
749757
750758
Parameters
751759
----------
752-
filename : string
753-
Name of the concentration file
754-
text : string
755-
Name of the text string in the ucn file. Default is 'CONCENTRATION'
756-
precision : string
757-
'auto', 'single' or 'double'. Default is 'auto'.
758-
verbose : bool
759-
Write information to the screen. Default is False.
760+
filename : str or PathLike
761+
Path of the concentration file.
762+
text : str
763+
Ignored.
764+
precision : {'auto', 'single', 'double'}
765+
Precision of floating point values. Default 'auto' enables automatic
766+
detection of precision.
767+
verbose : bool, default False
768+
Write information to the screen.
760769
761770
Attributes
762771
----------
@@ -792,12 +801,11 @@ class UcnFile(BinaryLayerFile):
792801
def __init__(
793802
self,
794803
filename,
795-
text="concentration",
804+
text="concentration", # noqa ARG002
796805
precision="auto",
797806
verbose=False,
798807
**kwargs,
799808
):
800-
self.text = text.encode()
801809
if precision == "auto":
802810
precision = get_headfile_precision(filename)
803811
if precision == "unknown":
@@ -821,14 +829,13 @@ class HeadUFile(BinaryLayerFile):
821829
----------
822830
filename : str or PathLike
823831
Path of the head file
824-
text : string
825-
Name of the text string in the head file. Default is 'headu'.
826-
precision : string
827-
Precision of the floating point head data in the file. Accepted
828-
values are 'auto', 'single' or 'double'. Default is 'auto', which
829-
enables precision to be automatically detected.
830-
verbose : bool
831-
Toggle logging output. Default is False.
832+
text : str
833+
Ignored.
834+
precision : {'auto', 'single', 'double'}
835+
Precision of floating point values. Default 'auto' enables automatic
836+
detection of precision.
837+
verbose : bool, default False
838+
Toggle logging output.
832839
833840
Notes
834841
-----
@@ -859,15 +866,14 @@ class HeadUFile(BinaryLayerFile):
859866
def __init__(
860867
self,
861868
filename: Union[str, os.PathLike],
862-
text="headu",
869+
text="headu", # noqa ARG002
863870
precision="auto",
864871
verbose=False,
865872
**kwargs,
866873
):
867874
"""
868875
Class constructor
869876
"""
870-
self.text = text.encode()
871877
if precision == "auto":
872878
precision = get_headfile_precision(filename)
873879
if precision == "unknown":
@@ -990,11 +996,11 @@ class CellBudgetFile:
990996
----------
991997
filename : str or PathLike
992998
Path of the cell budget file.
993-
precision : string
994-
Precision of floating point budget data in the file. Accepted
995-
values are 'single' or 'double'. Default is 'single'.
996-
verbose : bool
997-
Toggle logging output. Default is False.
999+
precision : {'auto', 'single', 'double'}
1000+
Precision of floating point values. Default 'auto' enables automatic
1001+
detection of precision.
1002+
verbose : bool, default False
1003+
Toggle logging output.
9981004
9991005
Examples
10001006
--------
@@ -2217,7 +2223,6 @@ def reverse(self, filename: Optional[os.PathLike] = None):
22172223
22182224
Parameters
22192225
----------
2220-
22212226
filename : str or PathLike, optional
22222227
Path of the new reversed binary cell budget file to create.
22232228
"""

0 commit comments

Comments
 (0)