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

58 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-16 11:25 +0200

1import pathlib 

2from typing import List, Optional, Tuple 

3 

4import numpy as np 

5 

6import imod 

7from imod.logging import init_log_decorator 

8from imod.mf6.interfaces.imaskingsettings import IMaskingSettings 

9from imod.mf6.interfaces.iregridpackage import IRegridPackage 

10from imod.mf6.package import Package 

11from imod.mf6.utilities.regrid import RegridderType 

12from imod.mf6.validation import DisBottomSchema 

13from imod.schemata import ( 

14 ActiveCellsConnectedSchema, 

15 AllValueSchema, 

16 AnyValueSchema, 

17 DimsSchema, 

18 DTypeSchema, 

19 IdentityNoDataSchema, 

20 IndexesSchema, 

21) 

22 

23 

24class 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