Source code for imod.mf6.npf

import warnings
from copy import deepcopy
from typing import Optional

import numpy as np
import xarray as xr

from imod.logging import init_log_decorator
from imod.mf6.interfaces.iregridpackage import IRegridPackage
from imod.mf6.package import Package
from imod.mf6.regrid.regrid_schemes import (
    NodePropertyFlowRegridMethod,
)
from imod.mf6.utilities.imod5_converter import fill_missing_layers
from imod.mf6.utilities.regrid import (
    RegridderWeightsCache,
    _regrid_package_data,
)
from imod.mf6.validation import PKG_DIMS_SCHEMA
from imod.schemata import (
    AllValueSchema,
    CompatibleSettingsSchema,
    DimsSchema,
    DTypeSchema,
    IdentityNoDataSchema,
    IndexesSchema,
)
from imod.typing import GridDataArray
from imod.typing.grid import zeros_like


def _dataarray_to_bool(griddataarray: GridDataArray) -> bool:
    if griddataarray is None or griddataarray.values is None:
        return False

    if griddataarray.values.size != 1:
        raise ValueError("DataArray is not a single value")

    if griddataarray.values.dtype != bool:
        raise ValueError("DataArray is not a boolean")

    return griddataarray.values.item()


[docs] class NodePropertyFlow(Package, IRegridPackage): """ Node Property Flow package. In this package the hydraulic conductivity and rewetting in the model is specified. A single NPF Package is required for each GWF model. https://water.usgs.gov/water-resources/software/MODFLOW-6/mf6io_6.0.4.pdf#page=51 A note about regridding: the fields k, k22, k33 define the principal components of an anisotropic conductivity tensor. By default, k and k22 are in the horizontal plane and k33 is vertical. Angle1, angle2 and angle3 define the rotation of this tensor. The regridding methods associated by default are chosen based on the assumption that k and k22 are horizontal and k33 is vertical. If this is not the case, it is up to the user to regrid the npf package using other regridding methods. This may be recommended if for example the rotation is such that k has become vertical and k33 horizontal. Parameters ---------- icelltype: array of int (xr.DataArray) flag for each cell that specifies how saturated thickness is treated. 0 means saturated thickness is held constant; >0 means saturated thickness varies with computed head when head is below the cell top; <0 means saturated thickness varies with computed head unless the starting_head_as_confined_thickness option is in effect. When starting_head_as_confined_thickness is in effect, a negative value of icelltype indicates that saturated thickness will be computed as strt-bot and held constant. k: array of floats (xr.DataArray) is the hydraulic conductivity. For the common case in which the user would like to specify the horizontal hydraulic conductivity and the vertical hydraulic conductivity, then K should be assigned as the horizontal hydraulic conductivity, K33 should be assigned as the vertical hydraulic conductivity, and K22 and the three rotation angles should not be specified. When more sophisticated anisotropy is required, then K corresponds to the K11 hydraulic conductivity axis. All included cells (idomain > 0) must have a K value greater than zero rewet: ({True, False}, optional) activates model rewetting. Default is False. rewet_layer: float is a combination of the wetting threshold and a flag to indicate which neighboring cells can cause a cell to become wet. If rewet_layer < 0, only a cell below a dry cell can cause the cell to become wet. If rewet_layer > 0, the cell below a dry cell and horizontally adjacent cells can cause a cell to become wet. If rewet_layer is 0, the cell cannot be wetted. The absolute value of rewet_layer is the wetting threshold. When the sum of BOT and the absolute value of rewet_layer at a dry cell is equaled or exceeded by the head at an adjacent cell, the cell is wetted. rewet_layer must be specified if "rewet" is specified in the OPTIONS block. If "rewet" is not specified in the options block, then rewet_layer can be entered, and memory will be allocated for it, even though it is not used. (WETDRY) Default is None. rewet_factor: is a keyword and factor that is included in the calculation of the head that is initially established at a cell when that cell is converted from dry to wet. (WETFCT) Default is None. rewet_iterations: is a keyword and iteration interval for attempting to wet cells. Wetting is attempted every rewet_iterations iteration. This applies to outer iterations and not inner iterations. If rewet_iterations is specified as zero or less, then the value is changed to 1. (IWETIT) Default is None. rewet_method: is a keyword and integer flag that determines which equation is used to define the initial head at cells that become wet. If rewet_method is 0, h = BOT + rewet_factor (hm - BOT). If rewet_method is not 0, h = BOT + rewet_factor (THRESH). (IHDWET) Default is None. k22: array of floats (xr.DataArray) is the hydraulic conductivity of the second ellipsoid axis; for an unrotated case this is the hydraulic conductivity in the y direction. If K22 is not included, then K22 is set equal to K. For a regular MODFLOW grid (DIS Package is used) in which no rotation angles are specified, K22 is the hydraulic conductivity along columns in the y direction. For an unstructured DISU grid, the user must assign principal x and y axes and provide the angle for each cell face relative to the assigned x direction. All included cells (idomain > 0) must have a K22 value greater than zero. Default is None. k33: array of floats (xr.DataArray) is the hydraulic conductivity of the third ellipsoid axis; for an unrotated case, this is the vertical hydraulic conductivity. When anisotropy is applied, K33 corresponds to the K33 tensor component. All included cells (idomain > 0) must have a K33 value greater than zero. Default is None. angle1: float is a rotation angle of the hydraulic conductivity tensor in degrees. The angle represents the first of three sequential rotations of the hydraulic conductivity ellipsoid. With the K11, K22, and K33 axes of the ellipsoid initially aligned with the x, y, and z coordinate axes, respectively, angle1 rotates the ellipsoid about its K33 axis (within the x - y plane). A positive value represents counter-clockwise rotation when viewed from any point on the positive K33 axis, looking toward the center of the ellipsoid. A value of zero indicates that the K11 axis lies within the x - z plane. If angle1 is not specified, default values of zero are assigned to angle1, angle2, and angle3, in which case the K11, K22, and K33 axes are aligned with the x, y, and z axes, respectively. Default is None. angle2: float is a rotation angle of the hydraulic conductivity tensor in degrees. The angle represents the second of three sequential rotations of the hydraulic conductivity ellipsoid. Following the rotation by angle1 described above, angle2 rotates the ellipsoid about its K22 axis (out of the x - y plane). An array can be specified for angle2 only if angle1 is also specified. A positive value of angle2 represents clockwise rotation when viewed from any point on the positive K22 axis, looking toward the center of the ellipsoid. A value of zero indicates that the K11 axis lies within the x - y plane. If angle2 is not specified, default values of zero are assigned to angle2 and angle3; connections that are not user-designated as vertical are assumed to be strictly horizontal (that is, to have no z component to their orientation); and connection lengths are based on horizontal distances. Default is None. angle3: float is a rotation angle of the hydraulic conductivity tensor in degrees. The angle represents the third of three sequential rotations of the hydraulic conductivity ellipsoid. Following the rotations by angle1 and angle2 described above, angle3 rotates the ellipsoid about its K11 axis. An array can be specified for angle3 only if angle1 and angle2 are also specified. An array must be specified for angle3 if angle2 is specified. A positive value of angle3 represents clockwise rotation when viewed from any point on the positive K11 axis, looking toward the center of the ellipsoid. A value of zero indicates that the K22 axis lies within the x - y plane. Default is None. alternative_cell_averaging : str Method calculating horizontal cell connection conductance. Options: {"LOGARITHMIC", "AMT-LMK", or "AMT-HMK"} Default: uses harmonic mean for averaging save_flows: ({True, False}, optional) keyword to indicate that cell-by-cell flow terms will be written to the file specified with "budget save file" in Output Control. Default is False. starting_head_as_confined_thickness: ({True, False}, optional) indicates that cells having a negative icelltype are confined, and their cell thickness for conductance calculations will be computed as strt-bot rather than top-bot. (THICKSTRT) Default is False. variable_vertical_conductance: ({True, False}, optional) keyword to indicate that the vertical conductance will be calculated using the saturated thickness and properties of the overlying cell and the thickness and properties of the underlying cell. if the dewatered keyword is also specified, then the vertical conductance is calculated using only the saturated thickness and properties of the overlying cell if the head in the underlying cell is below its top. if these keywords are not specified, then the default condition is to calculate the vertical conductance at the start of the simulation using the initial head and the cell properties. the vertical conductance remains constant for the entire simulation. (VARIABLECV) Default is False. dewatered: ({True, False}, optional) If the dewatered keyword is specified, then the vertical conductance is calculated using only the saturated thickness and properties of the overlying cell if the head in the underlying cell is below its top. Default is False. perched: ({True, False}, optional) keyword to indicate that when a cell is overlying a dewatered convertible cell, the head difference used in Darcy’s Law is equal to the head in the overlying cell minus the bottom elevation of the overlying cell. If not specified, then the default is to use the head difference between the two cells. Default is False. save_specific_discharge: ({True, False}, optional) keyword to indicate that x, y, and z components of specific discharge will be calculated at cell centers and written to the cell-by-cell flow file, which is specified with"budget save file" in Output Control. If this option is activated, then additional information may be required in the discretization packages and the GWF Exchange package (if GWF models are coupled). Specifically, angldegx must be specified in the connectiondata block of the disu package; angldegx must also be specified for the GWF Exchange as an auxiliary variable. disu package has not been implemented yet. Default is False. save_saturation: ({True, False}, optional) keyword to indicate that cell saturation will be written to the budget file, which is specified with "BUDGET SAVE FILE" in Output Control. Saturation will be saved to the budget file as an auxiliary variable saved with the DATA-SAT text label. Saturation is a cell variable that ranges from zero to one and can be used by post processing programs to determine how much of a cell volume is saturated. If ICELLTYPE is 0, then saturation is always one. xt3d_option: ({True, False}, optional) If True, the XT3D formulation will be used. By default False. rhs_option: ({True, False}, optional) If True, then the XT3D additional terms will be added to the right-hand side. If False, then the XT3D terms will be put into the coefficient matrix. By default False. validate: {True, False} Flag to indicate whether the package should be validated upon initialization. This raises a ValidationError if package input is provided in the wrong manner. Defaults to True. """ _pkg_id = "npf" _init_schemata = { "icelltype": [ DTypeSchema(np.integer), IndexesSchema(), PKG_DIMS_SCHEMA, ], "k": [ DTypeSchema(np.floating), IndexesSchema(), PKG_DIMS_SCHEMA, ], "rewet_layer": [ DTypeSchema(np.floating), IndexesSchema(), PKG_DIMS_SCHEMA, ], "k22": [ DTypeSchema(np.floating), IndexesSchema(), PKG_DIMS_SCHEMA, ], "k33": [ DTypeSchema(np.floating), IndexesSchema(), PKG_DIMS_SCHEMA, ], "angle1": [ DTypeSchema(np.floating), IndexesSchema(), PKG_DIMS_SCHEMA, ], "angle2": [ DTypeSchema(np.floating), IndexesSchema(), PKG_DIMS_SCHEMA, ], "angle3": [ DTypeSchema(np.floating), IndexesSchema(), PKG_DIMS_SCHEMA, ], "alternative_cell_averaging": [ DTypeSchema(str), DimsSchema(), CompatibleSettingsSchema("xt3d_option", False), ], "rhs_option": [ DTypeSchema(np.bool_), DimsSchema(), CompatibleSettingsSchema("xt3d_option", True), ], "save_flows": [DTypeSchema(np.bool_), DimsSchema()], "starting_head_as_confined_thickness": [DTypeSchema(np.bool_)], "variable_vertical_conductance": [DTypeSchema(np.bool_)], "dewatered": [DTypeSchema(np.bool_)], "perched": [DTypeSchema(np.bool_)], "save_specific_discharge": [DTypeSchema(np.bool_), DimsSchema()], } _write_schemata = { "k": ( AllValueSchema(">", 0.0), IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0)), ), "rewet_layer": ( IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0)), ), "k22": ( AllValueSchema(">", 0.0), IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0)), # No need to check coords: dataset ensures they align with idomain. ), "k33": ( AllValueSchema(">", 0.0), IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0)), # No need to check coords: dataset ensures they align with idomain. ), "angle1": (IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0)),), "angle2": (IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0)),), "angle3": (IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0)),), } _grid_data = { "icelltype": np.int32, "k": np.float64, "rewet_layer": np.float64, "k22": np.float64, "k33": np.float64, "angle1": np.float64, "angle2": np.float64, "angle3": np.float64, } _keyword_map = { "rewet": "rewet_record", "rewet_factor": "wetfct", "rewet_method": "ihdwet", "rewet_layer": "wetdry", "variable_vertical_conductance": "variablecv", "starting_head_as_confined_thickness": "thickstrt", "rewet_iterations": "iwetit", "xt3d_option": "xt3doptions", "rhs_option": "rhs", } _template = Package._initialize_template(_pkg_id) _regrid_method = NodePropertyFlowRegridMethod()
[docs] @init_log_decorator() def __init__( self, icelltype, k, rewet=False, rewet_layer=None, rewet_factor=None, rewet_iterations=None, rewet_method=None, k22=None, k33=None, angle1=None, angle2=None, angle3=None, cell_averaging=None, alternative_cell_averaging=None, save_flows=False, starting_head_as_confined_thickness=False, variable_vertical_conductance=False, dewatered=False, perched=False, save_specific_discharge=False, save_saturation=False, xt3d_option=False, rhs_option=False, validate: bool = True, ): # check rewetting if not rewet and any( [rewet_layer, rewet_factor, rewet_iterations, rewet_method] ): raise ValueError( "rewet_layer, rewet_factor, rewet_iterations, and rewet_method should" " all be left at a default value of None if rewet is False." ) if cell_averaging is not None: warnings.warn( "Use of `cell_averaging` is deprecated, please use `alternative_cell_averaging` instead", DeprecationWarning, ) alternative_cell_averaging = cell_averaging dict_dataset = { "icelltype": icelltype, "k": k, "rewet": rewet, "rewet_layer": rewet_layer, "rewet_factor": rewet_factor, "rewet_iterations": rewet_iterations, "rewet_method": rewet_method, "k22": k22, "k33": k33, "angle1": angle1, "angle2": angle2, "angle3": angle3, "alternative_cell_averaging": alternative_cell_averaging, "save_flows": save_flows, "starting_head_as_confined_thickness": starting_head_as_confined_thickness, "variable_vertical_conductance": variable_vertical_conductance, "dewatered": dewatered, "perched": perched, "save_specific_discharge": save_specific_discharge, "save_saturation": save_saturation, "xt3d_option": xt3d_option, "rhs_option": rhs_option, } super().__init__(dict_dataset) self._validate_init_schemata(validate)
def get_xt3d_option(self) -> bool: """ Returns the xt3d option value for this object. """ return _dataarray_to_bool(self.dataset["xt3d_option"]) def set_xt3d_option(self, is_xt3d_used: bool, is_rhs: bool) -> None: """ Returns the xt3d option value for this object. """ self.dataset["rhs_option"] = is_rhs self.dataset["xt3d_option"] = is_xt3d_used @property def is_variable_vertical_conductance(self) -> bool: """ Returns the VariableCV option value for this object. """ return _dataarray_to_bool(self.dataset["variable_vertical_conductance"]) @property def is_dewatered(self) -> bool: """ Returns the "dewatered" option value for this object. Used only when variable_vertical_conductance is true """ return _dataarray_to_bool(self.dataset["dewatered"]) def _validate(self, schemata, **kwargs): # Insert additional kwargs kwargs["xt3d_option"] = self["xt3d_option"] errors = super()._validate(schemata, **kwargs) return errors
[docs] @classmethod def from_imod5_data( cls, imod5_data: dict[str, dict[str, GridDataArray]], target_grid: GridDataArray, regridder_types: Optional[NodePropertyFlowRegridMethod] = None, regrid_cache: RegridderWeightsCache = RegridderWeightsCache(), ) -> "NodePropertyFlow": """ Construct an npf-package from iMOD5 data, loaded with the :func:`imod.formats.prj.open_projectfile_data` function. .. note:: The method expects the iMOD5 model to be fully 3D, not quasi-3D. Parameters ---------- imod5_data: dict Dictionary with iMOD5 data. This can be constructed from the :func:`imod.formats.prj.open_projectfile_data` method. target_grid: GridDataArray The grid that should be used for the new package. Does not need to be identical to one of the input grids. regridder_types: RegridMethodType, optional Optional dataclass with regridder types for a specific variable. Use this to override default regridding methods. regrid_cache: RegridderWeightsCache, optional stores regridder weights for different regridders. Can be used to speed up regridding, if the same regridders are used several times for regridding different arrays. Returns ------- Modflow 6 npf package. """ data = { "k": imod5_data["khv"]["kh"], } has_vertical_anisotropy = ( "kva" in imod5_data.keys() and "vertical_anisotropy" in imod5_data["kva"].keys() ) has_horizontal_anisotropy = "ani" in imod5_data.keys() if has_vertical_anisotropy: data["k33"] = data["k"] * imod5_data["kva"]["vertical_anisotropy"] if has_horizontal_anisotropy: if not np.all(np.isnan(imod5_data["ani"]["factor"].values)): factor = imod5_data["ani"]["factor"] factor = fill_missing_layers(factor, target_grid, 1) data["k22"] = data["k"] * factor if not np.all(np.isnan(imod5_data["ani"]["angle"].values)): angle1 = imod5_data["ani"]["angle"] angle1 = 90.0 - angle1 angle1 = xr.where(angle1 < 0, 360.0 + angle1, angle1) angle1 = fill_missing_layers(angle1, target_grid, 0) data["angle1"] = angle1 icelltype = zeros_like(target_grid, dtype=int) if regridder_types is None: regridder_types = NodePropertyFlow.get_regrid_methods() new_package_data = _regrid_package_data( data, target_grid, regridder_types, regrid_cache, {} ) new_package_data["icelltype"] = icelltype return NodePropertyFlow(**new_package_data, validate=True)
@classmethod def get_regrid_methods(cls) -> NodePropertyFlowRegridMethod: return deepcopy(cls._regrid_method)