Coverage for C:\src\imod-python\imod\mf6\dis.py: 93%

1 import pathlib

2 from typing import List, Optional, Tuple

3

4 import numpy as np

5

6 import imod

7 from imod.logging import init_log_decorator

8 from imod.mf6.interfaces.imaskingsettings import IMaskingSettings

9 from imod.mf6.interfaces.iregridpackage import IRegridPackage

10 from imod.mf6.package import Package

11 from imod.mf6.utilities.regrid import RegridderType

12 from imod.mf6.validation import DisBottomSchema

13 from imod.schemata import (

14 ActiveCellsConnectedSchema,

15 AllValueSchema,

16 AnyValueSchema,

17 DimsSchema,

18 DTypeSchema,

19 IdentityNoDataSchema,

20 IndexesSchema,

21)

22

23

24 class StructuredDiscretization(Package, IRegridPackage, IMaskingSettings):

25 """

26 Discretization information for structered grids is specified using the file.

27 (DIS6) Only one discretization input file (DISU6, DISV6 or DIS6) can be

28 specified for a model.

29 https://water.usgs.gov/water-resources/software/MODFLOW-6/mf6io_6.0.4.pdf#page=35

30

31 Parameters

32 ----------

33 top: array of floats (xr.DataArray)

34 is the top elevation for each cell in the top model layer.

35 bottom: array of floats (xr.DataArray)

36 is the bottom elevation for each cell.

37 idomain: array of integers (xr.DataArray)

38 Indicates the existence status of a cell. Horizontal discretization

39 information will be derived from the x and y coordinates of the

40 DataArray. If the idomain value for a cell is 0, the cell does not exist

41 in the simulation. Input and output values will be read and written for

42 the cell, but internal to the program, the cell is excluded from the

43 solution. If the idomain value for a cell is 1, the cell exists in the

44 simulation. if the idomain value for a cell is -1, the cell does not

45 exist in the simulation. Furthermore, the first existing cell above will

46 be connected to the first existing cell below. This type of cell is

47 referred to as a "vertical pass through" cell.

48 validate: {True, False}

49 Flag to indicate whether the package should be validated upon

50 initialization. This raises a ValidationError if package input is

51 provided in the wrong manner. Defaults to True.

52 """

53

54 _pkg_id = "dis"

55 _init_schemata = {

56 "top": [

57 DTypeSchema(np.floating),

58 DimsSchema("y", "x") | DimsSchema(),

59 IndexesSchema(),

60 ],

61 "bottom": [

62 DTypeSchema(np.floating),

63 DimsSchema("layer", "y", "x") | DimsSchema("layer"),

64 IndexesSchema(),

65 ],

66 "idomain": [

67 DTypeSchema(np.integer),

68 DimsSchema("layer", "y", "x"),

69 IndexesSchema(),

70 ],

71 }

72 _write_schemata = {

73 "idomain": (

74 ActiveCellsConnectedSchema(is_notnull=("!=", 0)),

75 AnyValueSchema(">", 0),

76 ),

77 "top": (

78 AllValueSchema(">", "bottom", ignore=("idomain", "==", -1)),

79 IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0)),

80 # No need to check coords: dataset ensures they align with idomain.

81 ),

82 "bottom": (DisBottomSchema(other="idomain", is_other_notnull=(">", 0)),),

83 }

84

85 _grid_data = {"top": np.float64, "bottom": np.float64, "idomain": np.int32}

86 _keyword_map = {"bottom": "botm"}

87 _template = Package._initialize_template(_pkg_id)

88

89 _regrid_method = {

90 "top": (RegridderType.OVERLAP, "mean"),

91 "bottom": (RegridderType.OVERLAP, "mean"),

92 "idomain": (RegridderType.OVERLAP, "mode"),

93 }

94

95 @property

96 def skip_variables(self) -> List[str]:

97 return ["bottom"]

98

99 @init_log_decorator()

100 def __init__(self, top, bottom, idomain, validate: bool = True):

101 dict_dataset = {

102 "idomain": idomain,

103 "top": top,

104 "bottom": bottom,

105 }

106 super().__init__(dict_dataset)

107 self._validate_init_schemata(validate)

108

109 def _delrc(self, dx):

110 """

111 dx means dx or dy

112 """

113 if isinstance(dx, (int, float)):

114 return f"constant {dx}"

115 elif isinstance(dx, np.ndarray):

116 arrstr = str(dx)[1:-1]

117 return f"internal\n {arrstr}"

118 else:

119 raise ValueError(f"Unhandled type of {dx}")

120

121 def render(self, directory, pkgname, globaltimes, binary):

122 disdirectory = pathlib.Path(directory) / pkgname

123 d = {}

124 x = self.dataset["idomain"].coords["x"]

125 y = self.dataset["idomain"].coords["y"]

126 dx, xmin, _ = imod.util.spatial.coord_reference(x)

127 dy, ymin, _ = imod.util.spatial.coord_reference(y)

128

129 d["xorigin"] = xmin

130 d["yorigin"] = ymin

131 d["nlay"] = self.dataset["idomain"].coords["layer"].size

132 d["nrow"] = y.size

133 d["ncol"] = x.size

134 d["delr"] = self._delrc(np.abs(dx))

135 d["delc"] = self._delrc(np.abs(dy))

136 _, d["top"] = self._compose_values(

137 self["top"], disdirectory, "top", binary=binary

138 )

139 d["botm_layered"], d["botm"] = self._compose_values(

140 self["bottom"], disdirectory, "botm", binary=binary

141 )

142 d["idomain_layered"], d["idomain"] = self._compose_values(

143 self["idomain"], disdirectory, "idomain", binary=binary

144 )

145

146 return self._template.render(d)

147

148 def _validate(self, schemata, **kwargs):

149 # Insert additional kwargs

150 kwargs["bottom"] = self["bottom"]

151 errors = super()._validate(schemata, **kwargs)

152

153 return errors

154

155 def get_regrid_methods(self) -> Optional[dict[str, Tuple[RegridderType, str]]]:

156 return self._regrid_method