diff --git a/docs/api/changelog.rst b/docs/api/changelog.rst index 9942338e7..de7d1156c 100644 --- a/docs/api/changelog.rst +++ b/docs/api/changelog.rst @@ -56,6 +56,8 @@ Fixed a layer in the model discretization, which would cause these cells to be dropped when distributing conductances later. - Fixed :func:`imod.prepare.spatial.polygonize` for polygons with holes. +- :func:`imod.formats.prj.open_projectfile_data` now drops empty wells from the + dataset, and logs a warning about it. Changed ~~~~~~~ diff --git a/imod/formats/prj/prj.py b/imod/formats/prj/prj.py index da51a90cb..8bb1e1887 100644 --- a/imod/formats/prj/prj.py +++ b/imod/formats/prj/prj.py @@ -915,6 +915,15 @@ def _read_package_ipf( # Ensure the columns are identifiable. path = Path(entry["path"]) ipf_df, indexcol, ext = _try_read_with_func(imod.ipf._read_ipf, path) + nrow = ipf_df.shape[0] + if nrow == 0: + log_message = f"IPF file {path} contains no data. Skipping." + imod.logging.logger.log( + loglevel=LogLevel.WARNING, + message=log_message, + additional_depth=0, + ) + continue if indexcol == 0: # No associated files has_associated = False diff --git a/imod/tests/conftest.py b/imod/tests/conftest.py index 0670d85d0..9bc606476 100644 --- a/imod/tests/conftest.py +++ b/imod/tests/conftest.py @@ -29,6 +29,7 @@ ) from .fixtures.imod5_well_data import ( well_duplication_import_prj, + well_empty_ipfs, well_mixed_ipfs, well_out_of_bounds_ipfs, well_regular_import_prj, diff --git a/imod/tests/fixtures/imod5_well_data.py b/imod/tests/fixtures/imod5_well_data.py index 4ca196614..a3e24de47 100644 --- a/imod/tests/fixtures/imod5_well_data.py +++ b/imod/tests/fixtures/imod5_well_data.py @@ -188,6 +188,36 @@ def out_of_bounds_timeseries_string(): ) +def ipf_simple_empty(): + """ + Empty ipf file with only the header and zero rows. iMOD5 can generate these + files after clipping an existing database. + """ + ipf_simple_header_copy = "0" + ipf_simple_header[ipf_simple_header.find("\n") :] + + return textwrap.dedent( + f"""\ + {ipf_simple_header_copy} + """ + ) + + +def ipf_associated_empty(): + """ + Empty associated ipf file with only the header and zero rows. iMOD5 can generate these + files after clipping an existing database. + """ + ipf_associated_header_copy = ( + "0" + ipf_associated_header[ipf_associated_header.find("\n") :] + ) + + return textwrap.dedent( + f"""\ + {ipf_associated_header_copy} + """ + ) + + def write_ipf_and_maybe_assoc_files( tmp_path, projectfile_str, @@ -337,3 +367,14 @@ def well_out_of_bounds_ipfs(): other_timeseries_well_str, tmp_path, ) + + +@pytest.fixture(scope="session") +def well_empty_ipfs(): + tmp_path = imod.util.temporary_directory() + os.makedirs(tmp_path) + + ipf1_str = ipf_simple_empty() + ipf_associated_str = ipf_associated_empty() + + return write_ipf_mixed_files(ipf_associated_str, ipf1_str, "", "", tmp_path) diff --git a/imod/tests/test_formats/test_prj_wel.py b/imod/tests/test_formats/test_prj_wel.py index e5e535743..ef90b7582 100644 --- a/imod/tests/test_formats/test_prj_wel.py +++ b/imod/tests/test_formats/test_prj_wel.py @@ -1,3 +1,4 @@ +import sys from datetime import datetime from shutil import copyfile from textwrap import dedent @@ -13,7 +14,8 @@ parametrize_with_cases, ) -from imod.formats.prj import open_projectfile_data +from imod.formats.prj import open_projectfile_data, read_projectfile +from imod.logging import LoggerType, LogLevel, configure from imod.mf6 import LayeredWell, Well @@ -946,3 +948,47 @@ def test_from_imod5_data_wells__wells_out_of_bounds( expected_last_rate = data[wellname]["dataframe"][0]["rate"].iloc[-2] actual_last_rate = well.dataset["rate"].isel(index=1, time=-1).item() assert actual_last_rate == expected_last_rate + + +@pytest.mark.unittest_jit +@parametrize("wel_case", argvalues=PRJ_ARGS) +@parametrize("wel_cls", argvalues=[LayeredWell, Well]) +def test_from_imod5_data_wells__empty_wells( + wel_cls: Union[LayeredWell, Well], + wel_case, + well_empty_ipfs, + tmp_path, + request, +): + # Arrange + # Replace layer number to zero if non-layered well. + if wel_cls == Well: + wel_case = wel_case.replace("1,2, 001", "1,2, 000") + # Write prj and copy ipfs to right folder. + case_name = get_case_name(request) + wel_file = tmp_path / f"{case_name}.prj" + setup_test_files(wel_case, wel_file, well_empty_ipfs, tmp_path) + + projectfile_contents = read_projectfile(wel_file) + + # Act + logfile_path = tmp_path / "logfile.txt" + with open(logfile_path, "w") as sys.stdout: + configure( + LoggerType.LOGURU, + log_level=LogLevel.WARNING, + add_default_file_handler=False, + add_default_stream_handler=True, + ) + data, _ = open_projectfile_data(wel_file) + + with open(logfile_path, "r") as f: + log = f.read() + + # Assert + # Projectfile only contains empty wells, so expect empty data. + assert len(data) == 0 + # Expect warning about empty wells. + for ipf_contents in projectfile_contents["(wel)"]["ipf"]: + ipf_path = ipf_contents["path"] + assert f"IPF file {ipf_path} contains no data. Skipping." in log