From c6aa66a94205c69b9a9c9b2f2a7cf58b660b4704 Mon Sep 17 00:00:00 2001 From: w-bonelli Date: Tue, 15 Nov 2022 10:25:26 -0500 Subject: [PATCH] test(utils): add tests for relocated utilities * add tests for grid and namefile utilities * stub tests for comparison and binary file utilities * mark get-modflow tests using GitHub API as slow * only skip test for generating classes from DFN if running in parallel --- autotest/generate_classes.py | 26 +++++++++++ autotest/test_binaryfile.py | 10 +++++ autotest/test_compare.py | 39 +++++++++++++++++ autotest/test_generate_classes.py | 16 ------- autotest/test_get_modflow.py | 2 + autotest/test_gridutil.py | 55 +++++++++++++++++++++++- autotest/test_mfreadnam.py | 34 +++++++++++++++ flopy/utils/mfreadnam.py | 71 ++++++++++++++++--------------- 8 files changed, 201 insertions(+), 52 deletions(-) create mode 100644 autotest/generate_classes.py create mode 100644 autotest/test_compare.py delete mode 100644 autotest/test_generate_classes.py create mode 100644 autotest/test_mfreadnam.py diff --git a/autotest/generate_classes.py b/autotest/generate_classes.py new file mode 100644 index 0000000000..d291a06be2 --- /dev/null +++ b/autotest/generate_classes.py @@ -0,0 +1,26 @@ +import os + +import pytest +from autotest.conftest import excludes_branch, get_project_root_path + +from flopy.mf6.utils import generate_classes + + +_project_root_path = get_project_root_path() +_using_xdist = bool(os.environ.get("PYTEST_XDIST_WORKER", None)) + + +@excludes_branch("master") +@pytest.mark.mf6 +@pytest.mark.skipif(_using_xdist, reason="modifies and reverts repository files, can't be run in parallel") +def test_generate_classes_from_dfn(): + try: + generate_classes(branch="develop", backup=False) + finally: + paths = [ + _project_root_path / "flopy" / "mf6" / "data" / "dfn", + _project_root_path / "flopy" / "mf6" / "modflow" + ] + # restoring might lose work if these files have been modified + for path in paths: + os.system(f"git restore {path}") diff --git a/autotest/test_binaryfile.py b/autotest/test_binaryfile.py index 3ded591e90..2dada82a51 100644 --- a/autotest/test_binaryfile.py +++ b/autotest/test_binaryfile.py @@ -233,3 +233,13 @@ def test_budgetfile_detect_precision_single(path): def test_budgetfile_detect_precision_double(path): file = CellBudgetFile(path, precision="auto") assert file.realtype == np.float64 + + +@pytest.mark.skip(reason="todo") +def test_write_head(): + pass + + +@pytest.mark.skip(reason="todo") +def test_write_budget(): + pass diff --git a/autotest/test_compare.py b/autotest/test_compare.py new file mode 100644 index 0000000000..2a9dc02210 --- /dev/null +++ b/autotest/test_compare.py @@ -0,0 +1,39 @@ +import pytest + +pytestmark = pytest.mark.skip(reason="todo") + + +def test_calculate_diffmax(): + pass + + +def test_calculate_difftol(): + pass + + +def test_eval_bud_diff(): + pass + + +def test_compare_budget(): + pass + + +def test_compare_swrbudget(): + pass + + +def test_compare_heads(): + pass + + +def test_compare_concs(): + pass + + +def test_compare_stages(): + pass + + +def test_compare(): + pass diff --git a/autotest/test_generate_classes.py b/autotest/test_generate_classes.py deleted file mode 100644 index 5c9388a995..0000000000 --- a/autotest/test_generate_classes.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest -from autotest.conftest import excludes_branch - -from flopy.mf6.utils import generate_classes - - -@pytest.mark.mf6 -@pytest.mark.skip( - reason="TODO: use external copy of the repo, otherwise files are rewritten" -) -@excludes_branch("master") -def test_generate_classes_from_dfn(): - # maybe compute hashes of files before/after - # generation to make sure they don't change? - - generate_classes(branch="develop", backup=False) diff --git a/autotest/test_get_modflow.py b/autotest/test_get_modflow.py index 8f0acdf824..4ba9aaa836 100644 --- a/autotest/test_get_modflow.py +++ b/autotest/test_get_modflow.py @@ -239,6 +239,7 @@ def test_script_options(tmpdir, downloads_dir): @flaky @requires_github +@pytest.mark.slow @pytest.mark.parametrize("repo", repo_options.keys()) def test_script(tmpdir, repo, downloads_dir): bindir = str(tmpdir) @@ -260,6 +261,7 @@ def test_script(tmpdir, repo, downloads_dir): @flaky @requires_github +@pytest.mark.slow @pytest.mark.parametrize("repo", repo_options.keys()) def test_python_api(tmpdir, repo, downloads_dir): bindir = str(tmpdir) diff --git a/autotest/test_gridutil.py b/autotest/test_gridutil.py index df897162aa..810d2a4a5f 100644 --- a/autotest/test_gridutil.py +++ b/autotest/test_gridutil.py @@ -1,6 +1,9 @@ +from itertools import product + +import numpy as np import pytest -from flopy.utils.gridutil import get_lni +from flopy.utils.gridutil import get_lni, get_disu_kwargs, uniform_flow_field @pytest.mark.parametrize( @@ -51,3 +54,53 @@ def test_get_lni_infers_layer_count_when_int_ncpl(ncpl, nodes, expected): assert isinstance(lni, list) for i, ln in enumerate(lni): assert ln == expected[i] + + +@pytest.mark.parametrize("nlay, nrow, ncol, delr, delc, tp, botm", + [ + (1, 61, 61, np.array(61 * [50]), np.array(61 * [50]), np.array([-10]), np.array([-30.0, -50.0])), + (2, 61, 61, np.array(61 * [50]), np.array(61 * [50]), np.array([-10]), np.array([-30.0, -50.0])), + ]) +def test_get_disu_kwargs(nlay, nrow, ncol, delr, delc, tp, botm): + kwargs = get_disu_kwargs(nlay=nlay, nrow=nrow, ncol=ncol, delr=delr, delc=delc, tp=tp, botm=botm) + + from pprint import pprint + pprint(kwargs["area"]) + + assert kwargs["nodes"] == nlay * nrow * ncol + assert kwargs["nvert"] == None + + area = np.array([dr * dc for (dr, dc) in product(delr, delc)], dtype=float) + area = np.array(nlay * [area]).flatten() + assert np.array_equal(kwargs["area"], area) + + # TODO: test other properties + # print(kwargs["iac"]) + # print(kwargs["ihc"]) + # print(kwargs["ja"]) + # print(kwargs["nja"]) + + +@pytest.mark.parametrize("qx, qy, qz, nlay, nrow, ncol", [ + (1, 0, 0, 1, 1, 10), + (0, 1, 0, 1, 1, 10), + (0, 0, 1, 1, 1, 10), + (1, 0, 0, 1, 10, 10), + (1, 0, 0, 2, 10, 10), + (1, 1, 0, 2, 10, 10), + (1, 1, 1, 2, 10, 10), + (2, 1, 1, 2, 10, 10) +]) +def test_uniform_flow_field(qx, qy, qz, nlay, nrow, ncol): + shape = nlay, nrow, ncol + spdis, flowja = uniform_flow_field(qx, qy, qz, shape) + + assert spdis.shape == (nlay * nrow * ncol,) + for i, t in enumerate(spdis.flatten()): + assert t[0] == t[1] == i + assert t[3] == qx + assert t[4] == qy + assert t[5] == qz + + # TODO: check flowja + # print(flowja.shape) diff --git a/autotest/test_mfreadnam.py b/autotest/test_mfreadnam.py new file mode 100644 index 0000000000..98a42799b8 --- /dev/null +++ b/autotest/test_mfreadnam.py @@ -0,0 +1,34 @@ +import pytest +from autotest.conftest import get_example_data_path +from flopy.utils.mfreadnam import get_entries_from_namefile + +_example_data_path = get_example_data_path() + + +@pytest.mark.parametrize("path", [ + _example_data_path / "mf6" / "test001a_Tharmonic" / "mfsim.nam", + _example_data_path / "mf6" / "test001e_UZF_3lay" / "mfsim.nam", + _example_data_path / "mf6-freyberg" / "mfsim.nam", +]) +def test_get_entries_from_namefile_mf6(path): + package = "IMS6" + entries = get_entries_from_namefile(path, ftype=package) + assert len(entries) == 1 + + entry = entries[0] + assert path.parent.name in entry[0] + assert entry[1] == package + + +@pytest.mark.skip(reason="only supports mf6 namefiles") +@pytest.mark.parametrize("path", [ + _example_data_path / "mf6-freyberg" / "freyberg.nam", +]) +def test_get_entries_from_namefile_mf2005(path): + package = "IC6" + entries = get_entries_from_namefile(path, ftype=package) + assert len(entries) == 1 + + entry = entries[0] + assert path.parent.name in entry[0] + assert entry[1] == package \ No newline at end of file diff --git a/flopy/utils/mfreadnam.py b/flopy/utils/mfreadnam.py index 9e017225d8..433c223c95 100644 --- a/flopy/utils/mfreadnam.py +++ b/flopy/utils/mfreadnam.py @@ -8,7 +8,9 @@ """ import os +from os import PathLike from pathlib import Path, PurePosixPath, PureWindowsPath +from typing import Tuple, List class NamData: @@ -269,13 +271,13 @@ def attribs_from_namfile_header(namefile): return defaults -def get_entries_from_namefile(namefile, ftype=None, unit=None, extension=None): - """Get entries from a namefile. Can select using FTYPE, UNIT, or file - extension. +def get_entries_from_namefile(path: PathLike, ftype: str = None, unit: int = None, extension: str = None) -> List[Tuple]: + """Get entries from an MF6 namefile. Can select using FTYPE, UNIT, or file extension. + This function only supports MF6 namefiles. Parameters ---------- - namefile : str + path : str path to a MODFLOW-based model name file ftype : str package type @@ -288,39 +290,38 @@ def get_entries_from_namefile(namefile, ftype=None, unit=None, extension=None): ------- entries : list of tuples list of tuples containing FTYPE, UNIT, FNAME, STATUS for each - namefile entry that meets a user-specified value. + namefile entry that meets a user-specified value. if no entries + are found, a single-element list containing a None-valued tuple is returned. """ entries = [] - f = open(namefile, "r") - for line in f: - if line.strip() == "": - continue - if line[0] == "#": - continue - ll = line.strip().split() - if len(ll) < 3: - continue - status = "UNKNOWN" - if len(ll) > 3: - status = ll[3].upper() - if ftype is not None: - if ftype.upper() == ll[0].upper(): - filename = os.path.join(os.path.split(namefile)[0], ll[2]) - entries.append((filename, ll[0], ll[1], status)) - elif unit is not None: - if int(unit) == int(ll[1]): - filename = os.path.join(os.path.split(namefile)[0], ll[2]) - entries.append((filename, ll[0], ll[1], status)) - elif extension is not None: - filename = os.path.join(os.path.split(namefile)[0], ll[2]) - ext = os.path.splitext(filename)[1] - if len(ext) > 0: - if ext[0] == ".": - ext = ext[1:] - if extension.lower() == ext.lower(): + with open(path, "r") as f: + for line in f: + if line.strip() == "": + continue + if line[0] == "#": + continue + ll = line.strip().split() + if len(ll) < 3: + continue + status = "UNKNOWN" + if len(ll) > 3: + status = ll[3].upper() + if ftype is not None: + if ftype.upper() in ll[0].upper(): + filename = os.path.join(os.path.split(path)[0], ll[2]) + entries.append((filename, ll[0], ll[1], status)) + elif unit is not None: + if int(unit) == int(ll[1]): + filename = os.path.join(os.path.split(path)[0], ll[2]) entries.append((filename, ll[0], ll[1], status)) - f.close() - if len(entries) < 1: - entries.append((None, None, None, None)) + elif extension is not None: + filename = os.path.join(os.path.split(path)[0], ll[2]) + ext = os.path.splitext(filename)[1] + if len(ext) > 0: + if ext[0] == ".": + ext = ext[1:] + if extension.lower() == ext.lower(): + entries.append((filename, ll[0], ll[1], status)) + return entries