Source code for imod.couplers.metamod.metamod

from pathlib import Path
from typing import Optional, Union

import tomli_w

from imod.couplers.metamod.node_svat_mapping import NodeSvatMapping
from imod.couplers.metamod.rch_svat_mapping import RechargeSvatMapping
from imod.couplers.metamod.wel_svat_mapping import WellSvatMapping
from imod.mf6 import Modflow6Simulation
from imod.mf6.model import Modflow6Model
from imod.msw import GridData, MetaSwapModel, Sprinkling


[docs]class MetaMod: """ The MetaMod class creates the necessary input files for coupling MetaSWAP to MODFLOW 6. Parameters ---------- msw_model : MetaSwapModel The MetaSWAP model that should be coupled. mf6_simulation : Modflow6Simulation The Modflow6 simulation that should be coupled. mf6_rch_pkgkey: str Key of Modflow 6 recharge package to which MetaSWAP is coupled. mf6_wel_pkgkey: str or None Optional key of Modflow 6 well package to which MetaSWAP sprinkling is coupled. """ _toml_name = "imod_coupler.toml" _modflow6_model_dir = "Modflow6" _metaswap_model_dir = "MetaSWAP"
[docs] def __init__( self, msw_model: MetaSwapModel, mf6_simulation: Modflow6Simulation, mf6_rch_pkgkey: str, mf6_wel_pkgkey: Optional[str] = None, ): self.msw_model = msw_model self.mf6_simulation = mf6_simulation self.mf6_rch_pkgkey = mf6_rch_pkgkey self.mf6_wel_pkgkey = mf6_wel_pkgkey self.is_sprinkling = self._check_coupler_and_sprinkling()
def _check_coupler_and_sprinkling(self): mf6_rch_pkgkey = self.mf6_rch_pkgkey mf6_wel_pkgkey = self.mf6_wel_pkgkey gwf_names = self._get_gwf_modelnames() # Assume only one groundwater flow model # FUTURE: Support multiple groundwater flow models. gwf_model = self.mf6_simulation[gwf_names[0]] if mf6_rch_pkgkey not in gwf_model.keys(): raise ValueError( f"No package named {mf6_rch_pkgkey} detected in Modflow 6 model. " "iMOD_coupler requires a Recharge package." ) sprinkling_key = self.msw_model._get_pkg_key(Sprinkling, optional_package=True) sprinkling_in_msw = sprinkling_key is not None sprinkling_in_mf6 = mf6_wel_pkgkey in gwf_model.keys() if sprinkling_in_msw and not sprinkling_in_mf6: raise ValueError( f"No package named {mf6_wel_pkgkey} found in Modflow 6 model, " "but Sprinkling package found in MetaSWAP. " "iMOD Coupler requires a Well Package " "to couple wells." ) elif not sprinkling_in_msw and sprinkling_in_mf6: raise ValueError( f"Modflow 6 Well package {mf6_wel_pkgkey} specified for sprinkling, " "but no Sprinkling package found in MetaSWAP model." ) elif sprinkling_in_msw and sprinkling_in_mf6: return True else: return False def write( self, directory: Union[str, Path], modflow6_dll: Union[str, Path], metaswap_dll: Union[str, Path], metaswap_dll_dependency: Union[str, Path], modflow6_write_kwargs: Optional[dict] = None, ): """ Write MetaSWAP and Modflow 6 model with exchange files, as well as a ``.toml`` file which configures the imod coupler run. Parameters ---------- directory: str or Path Directory in which to write the coupled models modflow6_dll: str or Path Path to modflow6 .dll. You can obtain this library by downloading `the last iMOD5 release <https://oss.deltares.nl/web/imod/download-imod5>`_ metaswap_dll: str or Path Path to metaswap .dll. You can obtain this library by downloading `the last iMOD5 release <https://oss.deltares.nl/web/imod/download-imod5>`_ metaswap_dll_dependency: str or Path Directory with metaswap .dll dependencies. Directory should contain: [fmpich2.dll, mpich2mpi.dll, mpich2nemesis.dll, TRANSOL.dll]. You can obtain these by downloading `the last iMOD5 release <https://oss.deltares.nl/web/imod/download-imod5>`_ modflow6_write_kwargs: dict Optional dictionary with keyword arguments for the writing of Modflow6 models. You can use this for example to turn off the validation at writing (``validation=False``) or to write text files (``binary=False``) """ if modflow6_write_kwargs is None: modflow6_write_kwargs = {} # force to Path directory = Path(directory) # For some reason the Modflow 6 model has to be written first, before # writing the MetaSWAP model. Else we get an Access Violation Error when # running the coupler. self.mf6_simulation.write( directory / self._modflow6_model_dir, **modflow6_write_kwargs, ) self.msw_model.write(directory / self._metaswap_model_dir) # Write exchange files exchange_dir = directory / "exchanges" exchange_dir.mkdir(mode=755, exist_ok=True) self.write_exchanges(exchange_dir, self.mf6_rch_pkgkey, self.mf6_wel_pkgkey) coupling_dict = self._get_coupling_dict( exchange_dir, self.mf6_rch_pkgkey, self.mf6_wel_pkgkey ) self.write_toml( directory, modflow6_dll, metaswap_dll, metaswap_dll_dependency, coupling_dict, ) def write_toml( self, directory: Union[str, Path], modflow6_dll: Union[str, Path], metaswap_dll: Union[str, Path], metaswap_dll_dependency: Union[str, Path], coupling_dict: dict, ): """ Write .toml file which configures the imod coupler run. Parameters ---------- directory: str or Path Directory in which to write the .toml file. modflow6_dll: str or Path Path to modflow6 .dll. You can obtain this library by downloading `the last iMOD5 release <https://oss.deltares.nl/web/imod/download-imod5>`_ metaswap_dll: str or Path Path to metaswap .dll. You can obtain this library by downloading `the last iMOD5 release <https://oss.deltares.nl/web/imod/download-imod5>`_ metaswap_dll_dependency: str or Path Directory with metaswap .dll dependencies. Directory should contain: [fmpich2.dll, mpich2mpi.dll, mpich2nemesis.dll, TRANSOL.dll]. You can obtain these by downloading `the last iMOD5 release <https://oss.deltares.nl/web/imod/download-imod5>`_ coupling_dict: dict Dictionary with names of coupler packages and paths to mappings. """ # force to Path directory = Path(directory) toml_path = directory / self._toml_name coupler_toml = { "timing": False, "log_level": "INFO", "driver_type": "metamod", "driver": { "kernels": { "modflow6": { "dll": str(modflow6_dll), "work_dir": f".\\{self._modflow6_model_dir}", }, "metaswap": { "dll": str(metaswap_dll), "work_dir": f".\\{self._metaswap_model_dir}", "dll_dep_dir": str(metaswap_dll_dependency), }, }, "coupling": [coupling_dict], }, } with open(toml_path, "wb") as f: tomli_w.dump(coupler_toml, f) def _get_gwf_modelnames(self): """ Get names of gwf models in mf6 simulation """ return [ key for key, value in self.mf6_simulation.items() if isinstance(value, Modflow6Model) ] def _get_coupling_dict( self, directory: Union[str, Path], mf6_rch_pkgkey: str, mf6_wel_pkgkey: Optional[str], ) -> dict: """ Get dictionary with names of coupler packages and paths to mappings. Parameters ---------- directory: str or Path Directory where .dxc files are written. mf6_rch_pkgkey: str Key of Modflow 6 recharge package to which MetaSWAP is coupled. mf6_wel_pkgkey: str Key of Modflow 6 well package to which MetaSWAP sprinkling is coupled. Returns ------- coupling_dict: dict Dictionary with names of coupler packages and paths to mappings. """ coupling_dict = {} gwf_names = self._get_gwf_modelnames() # Assume only one groundwater flow model # FUTURE: Support multiple groundwater flow models. coupling_dict["mf6_model"] = gwf_names[0] coupling_dict[ "mf6_msw_node_map" ] = f"./{directory.name}/{NodeSvatMapping._file_name}" coupling_dict["mf6_msw_recharge_pkg"] = mf6_rch_pkgkey coupling_dict[ "mf6_msw_recharge_map" ] = f"./{directory.name}/{RechargeSvatMapping._file_name}" coupling_dict["enable_sprinkling"] = self.is_sprinkling if self.is_sprinkling: coupling_dict["mf6_msw_well_pkg"] = mf6_wel_pkgkey coupling_dict[ "mf6_msw_sprinkling_map" ] = f"./{directory.name}/{WellSvatMapping._file_name}" return coupling_dict def write_exchanges( self, directory: Union[str, Path], mf6_rch_pkgkey: str, mf6_wel_pkgkey: Optional[str], ): """ Write exchange files (.dxc) which map MetaSWAP's svats to Modflow 6 node numbers, recharge ids, and well ids. Parameters ---------- directory: str or Path Directory where .dxc files are written. mf6_rch_pkgkey: str Key of Modflow 6 recharge package to which MetaSWAP is coupled. mf6_wel_pkgkey: str Key of Modflow 6 well package to which MetaSWAP sprinkling is coupled. """ gwf_names = self._get_gwf_modelnames() # Assume only one groundwater flow model # FUTURE: Support multiple groundwater flow models. gwf_model = self.mf6_simulation[gwf_names[0]] grid_data_key = [ pkgname for pkgname, pkg in self.msw_model.items() if isinstance(pkg, GridData) ][0] dis = gwf_model[gwf_model._get_pkgkey("dis")] index, svat = self.msw_model[grid_data_key].generate_index_array() grid_mapping = NodeSvatMapping(svat, dis) grid_mapping.write(directory, index, svat) recharge = gwf_model[mf6_rch_pkgkey] rch_mapping = RechargeSvatMapping(svat, recharge) rch_mapping.write(directory, index, svat) if self.is_sprinkling: well = gwf_model[mf6_wel_pkgkey] well_mapping = WellSvatMapping(svat, well) well_mapping.write(directory, index, svat)