From 544ef26abac6ce3e6e2c7010effc385a9a2a3aa6 Mon Sep 17 00:00:00 2001 From: Bas des Tombe Date: Tue, 28 May 2024 11:33:06 +0200 Subject: [PATCH] Consume version_tag arg when creating model_ds --- nlmod/dims/base.py | 23 ++++++++++++-- nlmod/sim/sim.py | 28 ++++++++++++---- nlmod/util.py | 79 +++++++++++++++++++++++++++++++++------------- 3 files changed, 99 insertions(+), 31 deletions(-) diff --git a/nlmod/dims/base.py b/nlmod/dims/base.py index 43085b10..e932107a 100644 --- a/nlmod/dims/base.py +++ b/nlmod/dims/base.py @@ -14,7 +14,9 @@ logger = logging.getLogger(__name__) -def set_ds_attrs(ds, model_name, model_ws, mfversion="mf6", exe_name=None): +def set_ds_attrs( + ds, model_name, model_ws, mfversion="mf6", exe_name=None, version_tag=None +): """Set the attribute of a model dataset. Parameters @@ -31,6 +33,11 @@ def set_ds_attrs(ds, model_name, model_ws, mfversion="mf6", exe_name=None): path to modflow executable, default is None, which assumes binaries are available in nlmod/bin directory. Binaries can be downloaded using `nlmod.util.download_mfbinaries()`. + version_tag : str, default None + GitHub release ID: for example "18.0" or "latest". If version_tag is provided, + the most recent installation location of MODFLOW is found in flopy metadata + that respects `version_tag`. If not found, the executables are downloaded. + Not compatible with exe_name. Returns ------- @@ -46,7 +53,9 @@ def set_ds_attrs(ds, model_name, model_ws, mfversion="mf6", exe_name=None): ds.attrs["created_on"] = dt.datetime.now().strftime(fmt) if exe_name is None: - exe_name = util.get_exe_path(exe_name=mfversion) + exe_name = util.get_exe_path(exe_name=mfversion, version_tag=version_tag) + else: + exe_name = util.get_exe_path(exe_name=exe_name, version_tag=version_tag) ds.attrs["exe_name"] = exe_name @@ -78,6 +87,7 @@ def to_model_ds( drop_attributes=True, transport=False, remove_nan_layers=True, + version_tag=None, ): """Transform an input dataset to a groundwater model dataset. @@ -136,6 +146,11 @@ def to_model_ds( remove_nan_layers : bool, optional if True remove layers with only nan values in the botm. Default is True. + version_tag : str, default None + GitHub release ID: for example "18.0" or "latest". If version_tag is provided, + the most recent installation location of MODFLOW is found in flopy metadata + that respects `version_tag`. If not found, the executables are downloaded. + Not compatible with exe_name. Returns ------- @@ -176,7 +191,9 @@ def to_model_ds( ds = extrapolate_ds(ds) # add attributes - ds = set_ds_attrs(ds, model_name, model_ws) + ds = set_ds_attrs( + ds, model_name, model_ws, mfversion="mf6", version_tag=version_tag + ) ds.attrs["transport"] = int(transport) # fill nan's diff --git a/nlmod/sim/sim.py b/nlmod/sim/sim.py index a690d038..052d2d9c 100644 --- a/nlmod/sim/sim.py +++ b/nlmod/sim/sim.py @@ -107,7 +107,7 @@ def get_tdis_perioddata(ds, nstp="nstp", tsmult="tsmult"): return tdis_perioddata -def sim(ds, exe_name=None): +def sim(ds, exe_name=None, version_tag=None): """create sim from the model dataset. Parameters @@ -117,9 +117,14 @@ def sim(ds, exe_name=None): attributes: model_name, mfversion, model_ws, time_units, start, perlen, nstp, tsmult exe_name: str, optional - path to modflow executable, default is None, which assumes binaries - are available in nlmod/bin directory. Binaries can be downloaded - using `nlmod.util.download_mfbinaries()`. + path to modflow executable, default is None. If None, the path is + obtained from the flopy metadata that respects `version_tag`. If not + found, the executables are downloaded. Not compatible with version_tag. + version_tag : str, default None + GitHub release ID: for example "18.0" or "latest". If version_tag is provided, + the most recent installation location of MODFLOW is found in flopy metadata + that respects `version_tag`. If not found, the executables are downloaded. + Not compatible with exe_name. Returns ------- @@ -130,8 +135,19 @@ def sim(ds, exe_name=None): # start creating model logger.info("creating mf6 SIM") - if exe_name is None: - exe_name = util.get_exe_path(exe_name=ds.mfversion) + # Most likely exe_name was previously set with to_model_ds() + if exe_name is not None: + exe_name = util.get_exe_path(exe_name=exe_name, version_tag=version_tag) + elif "exe_name" in ds.attrs: + exe_name = util.get_exe_path( + exe_name=ds.attrs["exe_name"], version_tag=version_tag + ) + elif "mfversion" in ds.attrs: + exe_name = util.get_exe_path( + exe_name=ds.attrs["mfversion"], version_tag=version_tag + ) + else: + raise ValueError("No exe_name provided and no exe_name found in ds.attrs") # Create the Flopy simulation object sim = flopy.mf6.MFSimulation( diff --git a/nlmod/util.py b/nlmod/util.py index 71f225af..17e54b17 100644 --- a/nlmod/util.py +++ b/nlmod/util.py @@ -104,6 +104,7 @@ def get_exe_path( """Get the full path of the executable. Searching for the executables is done in the following order: + 0. If exe_name is a full path, return the full path of the executable. 1. The directory specified with `bindir`. Raises error if exe_name is provided and not found. Requires enable_version_check to be False. 2. The directory used by nlmod installed in this environment. @@ -143,16 +144,29 @@ def get_exe_path( if sys.platform.startswith("win") and not exe_name.endswith(".exe"): exe_name += ".exe" - exe_full_path = str( - get_bin_directory( - exe_name=exe_name, - bindir=bindir, - download_if_not_found=download_if_not_found, - version_tag=version_tag, - repo=repo, + # If exe_name is a full path + if Path(exe_name).exists(): + enable_version_check = version_tag is not None and repo is not None + + if enable_version_check: + msg = ( + "Incompatible arguments. If exe_name is provided, unable to check " + "the version." + ) + raise ValueError(msg) + exe_full_path = exe_name + + else: + exe_full_path = str( + get_bin_directory( + exe_name=exe_name, + bindir=bindir, + download_if_not_found=download_if_not_found, + version_tag=version_tag, + repo=repo, + ) + / exe_name ) - / exe_name - ) msg = f"Executable path: {exe_full_path}" logger.debug(msg) @@ -171,6 +185,7 @@ def get_bin_directory( Get the directory where the executables are stored. Searching for the executables is done in the following order: + 0. If exe_name is a full path, return the full path of the executable. 1. The directory specified with `bindir`. Raises error if exe_name is provided and not found. Requires enable_version_check to be False. 2. The directory used by nlmod installed in this environment. @@ -218,8 +233,19 @@ def get_bin_directory( if sys.platform.startswith("win") and not exe_name.endswith(".exe"): exe_name += ".exe" - # If bindir is provided enable_version_check = version_tag is not None and repo is not None + + # If exe_name is a full path + if Path(exe_name).exists(): + if enable_version_check: + msg = ( + "Incompatible arguments. If exe_name is provided, unable to check " + "the version." + ) + raise ValueError(msg) + return Path(exe_name).parent + + # If bindir is provided if bindir is not None and enable_version_check: msg = "Incompatible arguments. If bindir is provided, unable to check the version." raise ValueError(msg) @@ -315,22 +341,31 @@ def get_flopy_bin_directories(version_tag=None, repo="executables"): meta_list = json.loads(meta_raw) enable_version_check = version_tag is not None and repo is not None - - # To convert latest into an explicit tag - if ( + SUPPRESS_EXE_VERION_CHECK = ( "NLMOD_SUPPRESS_EXE_VERION_CHECK" in os.environ and os.environ["NLMOD_SUPPRESS_EXE_VERION_CHECK"] - ).lower() in ("true", "1", "t"): - # envvars are always strings - suppress_version_check = True - logger.info( - "Suppressing version check by setting the NLMOD_SUPPRESS_EXE_VERION_CHECK " - "environment variable to True." + ).lower() in ("true", "1", "t") # envvars are always strings + + # To convert latest into an explicit tag + if enable_version_check and SUPPRESS_EXE_VERION_CHECK: + msg = ( + "The version of the executables would have been checked, because the " + "`version_tag` is passed to `get_flopy_bin_directories()`, but is " + "suppressed by a set NLMOD_SUPPRESS_EXE_VERION_CHECK." + ) + elif enable_version_check and not SUPPRESS_EXE_VERION_CHECK: + msg = ( + "The version of the executables will be checked, because the " + f"`version_tag={version_tag}` is passed to `get_flopy_bin_directories()`." ) else: - suppress_version_check = False + msg = ( + "The version of the executables will not be checked, because the " + "`version_tag` is not passed to `get_flopy_bin_directories()`." + ) + logger.info(msg) - if enable_version_check and not suppress_version_check: + if enable_version_check and not SUPPRESS_EXE_VERION_CHECK: version_tag_pin = get_release(tag=version_tag, repo=repo, quiet=True)[ "tag_name" ] @@ -353,7 +388,7 @@ def get_flopy_bin_directories(version_tag=None, repo="executables"): return path_list -def download_mfbinaries(bindir=None, version_tag=None, repo="executables"): +def download_mfbinaries(bindir=None, version_tag="latest", repo="executables"): """Download and unpack platform-specific modflow binaries. Source: USGS