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

55 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-08 13:27 +0200

1import pathlib 

2from typing import Optional, Tuple 

3 

4import numpy as np 

5 

6import imod 

7from imod.logging import init_log_decorator 

8from imod.mf6.interfaces.iregridpackage import IRegridPackage 

9from imod.mf6.package import Package 

10from imod.mf6.utilities.regrid import RegridderType 

11from imod.mf6.validation import DisBottomSchema 

12from imod.schemata import ( 

13 ActiveCellsConnectedSchema, 

14 AllValueSchema, 

15 AnyValueSchema, 

16 DimsSchema, 

17 DTypeSchema, 

18 IdentityNoDataSchema, 

19 IndexesSchema, 

20) 

21 

22 

23class StructuredDiscretization(Package, IRegridPackage): 

24 """ 

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

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

27 specified for a model. 

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

29 

30 Parameters 

31 ---------- 

32 top: array of floats (xr.DataArray) 

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

34 bottom: array of floats (xr.DataArray) 

35 is the bottom elevation for each cell. 

36 idomain: array of integers (xr.DataArray) 

37 Indicates the existence status of a cell. Horizontal discretization 

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

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

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

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

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

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

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

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

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

47 validate: {True, False} 

48 Flag to indicate whether the package should be validated upon 

49 initialization. This raises a ValidationError if package input is 

50 provided in the wrong manner. Defaults to True. 

51 """ 

52 

53 _pkg_id = "dis" 

54 _init_schemata = { 

55 "top": [ 

56 DTypeSchema(np.floating), 

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

58 IndexesSchema(), 

59 ], 

60 "bottom": [ 

61 DTypeSchema(np.floating), 

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

63 IndexesSchema(), 

64 ], 

65 "idomain": [ 

66 DTypeSchema(np.integer), 

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

68 IndexesSchema(), 

69 ], 

70 } 

71 _write_schemata = { 

72 "idomain": ( 

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

74 AnyValueSchema(">", 0), 

75 ), 

76 "top": ( 

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

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

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

80 ), 

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

82 } 

83 

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

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

86 _template = Package._initialize_template(_pkg_id) 

87 

88 _regrid_method = { 

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

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

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

92 } 

93 

94 _skip_mask_arrays = ["bottom"] 

95 

96 @init_log_decorator() 

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

98 dict_dataset = { 

99 "idomain": idomain, 

100 "top": top, 

101 "bottom": bottom, 

102 } 

103 super().__init__(dict_dataset) 

104 self._validate_init_schemata(validate) 

105 

106 def _delrc(self, dx): 

107 """ 

108 dx means dx or dy 

109 """ 

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

111 return f"constant {dx}" 

112 elif isinstance(dx, np.ndarray): 

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

114 return f"internal\n {arrstr}" 

115 else: 

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

117 

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

119 disdirectory = pathlib.Path(directory) / pkgname 

120 d = {} 

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

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

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

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

125 

126 d["xorigin"] = xmin 

127 d["yorigin"] = ymin 

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

129 d["nrow"] = y.size 

130 d["ncol"] = x.size 

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

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

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

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

135 ) 

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

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

138 ) 

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

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

141 ) 

142 

143 return self._template.render(d) 

144 

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

146 # Insert additional kwargs 

147 kwargs["bottom"] = self["bottom"] 

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

149 

150 return errors 

151 

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

153 return self._regrid_method