Coverage for C:\src\imod-python\imod\mf6\ist.py: 89%

1 from typing import Optional

2

3 import numpy as np

4 import xarray as xr

5

6 from imod.logging import init_log_decorator

7 from imod.mf6.package import Package

8 from imod.mf6.validation import PKG_DIMS_SCHEMA

9 from imod.schemata import (

10 AllValueSchema,

11 DTypeSchema,

12 IdentityNoDataSchema,

13 IndexesSchema,

14)

15

16

17 class ImmobileStorageTransfer(Package):

18 """

19 The Immobile Storage and Transfer (IST) package represents an immobile

20 fraction of groundwater. Any number of IST Packages can be specified for a

21 single GWT model. This allows the user to specify triple porosity systems,

22 or systems with as many immobile domains as necessary.

23

24 Parameters

25 ----------

26 initial_immmobile_concentration : array of floats (xr.DataArray)

27 initial concentration of the immobile domain in mass per length cubed

28 (cim).

29 immobile_porosity : array of floats (xr.DataArray)

30 porosity of the immobile domain specified as the volume of immobile

31 pore space per total volume (dimensionless) (thetaim).

32 mobile_immobile_mass_transfer_rate: array of floats (xr.DataArray)

33 mass transfer rate coefficient between the mobile and immobile domains,

34 in dimensions of per time (zetaim).

35 decay: array of floats (xr.DataArray).

36 is the rate coefficient for first or zero-order decay for the aqueous

37 phase of the immobile domain. A negative value indicates solute

38 production. The dimensions of decay for first-order decay is one over

39 time. The dimensions of decay for zero-order decay is mass per length

40 cubed per time. Decay will have no effect on simulation results unless

41 either first- or zero-order decay is specified in the options block.

42 decay_sorbed: array of floats (xr.DataArray)

43 is the rate coefficient for first or zero-order decay for the sorbed

44 phase of the immobile domain. A negative value indicates solute

45 production. The dimensions of decay_sorbed for first-order decay is one

46 over time. The dimensions of decay_sorbed for zero-order decay is mass

47 of solute per mass of aquifer per time. If decay_sorbed is not

48 specified and both decay and sorption are active, then the program will

49 terminate with an error. decay_sorbed will have no effect on simulation

50 results unless the SORPTION keyword and either first- or zero-order

51 decay are specified in the options block.

52 bulk_density: array of floats (xr.DataArray)

53 is the bulk density of the aquifer in mass per length cubed.

54 bulk_density will have no effect on simulation results unless the

55 SORPTION keyword is specified in the options block.

56 distribution_coefficient: array of floats (xr.DataArray)

57 is the distribution coefficient for the equilibrium-controlled linear

58 sorption isotherm in dimensions of length cubed per mass. distcoef will

59 have no effect on simulation results unless the SORPTION keyword is

60 specified in the options block.

61 save_flows: ({True, False}, optional)

62 Indicates that drain flow terms will be written to the file specified

63 with "BUDGET FILEOUT" in Output Control. Default is False.

64 budgetbinfile:

65 name of the binary output file to write budget information.

66 budgetcsvfile:

67 name of the comma-separated value (CSV) output file to write budget

68 summary information. A budget summary record will be written to this

69 file for each time step of the simulation.

70 sorption: ({True, False}, optional)

71 is a text keyword to indicate that sorption will be activated. Use of

72 this keyword requires that BULK_DENSITY and DISTCOEF are specified in

73 the GRIDDATA block. The linear sorption isotherm is the only isotherm

74 presently supported in the IST Package.

75 first_order_decay: ({True, False}, optional)

76 is a text keyword to indicate that first-order decay will occur. Use of

77 this keyword requires that DECAY and DECAY_SORBED (if sorption is

78 active) are specified in the GRIDDATA block.

79 zero_order_decay: ({True, False}, optional)

80 is a text keyword to indicate that zero-order decay will occur. Use of

81 this keyword requires that DECAY and DECAY_SORBED (if sorption is

82 active) are specified in the GRIDDATA block.

83 cimfile: (str)

84 name of the output file to write immobile concentrations. This file is

85 a binary file that has the same format and structure as a binary head

86 and concentration file. The value for the text variable written to the

87 file is CIM. Immobile domain concentrations will be written to this

88 file at the same interval as mobile domain concentrations are saved, as

89 specified in the GWT Model Output Control file.

90 columns: (int, optional), default is 10

91 number of columns for writing data.

92 width: (int, optional), default is 10

93 width for writing each number.

94 digits: (int, optional), default is 7

95 number of digits to use for writing a number.

96 format: (str, optional) default exponential

97 One of {"exponential", "fixed", "general", "scientific"}.

98 validate: {True, False}

99 Flag to indicate whether the package should be validated upon

100 initialization. This raises a ValidationError if package input is

101 provided in the wrong manner. Defaults to True.

102 """

103

104 _pkg_id = "ist"

105 _template = Package._initialize_template(_pkg_id)

106 _grid_data = {

107 "initial_immobile_concentration": np.float64,

108 "immobile_porosity": np.float64,

109 "mobile_immobile_mass_transfer_rate": np.float64,

110 "decay": np.float64,

111 "decay_sorbed": np.float64,

112 "bulk_density": np.float64,

113 "distribution_coefficient": np.float64,

114 }

115

116 _keyword_map = {

117 "initial_immobile_concentration": "cim",

118 "immobile_porosity": "thetaim",

119 "mobile_immobile_mass_transfer_rate": "zetaim",

120 "decay": "decay",

121 "decay_sorbed": "decay_sorbed",

122 "bulk_density": "bulk_density",

123 "distribution_coefficient": "distcoef",

124 }

125

126 _init_schemata = {

127 "initial_immobile_concentration": [

128 DTypeSchema(np.floating),

129 IndexesSchema(),

130 PKG_DIMS_SCHEMA,

131 ],

132 "immobile_porosity": [

133 DTypeSchema(np.floating),

134 IndexesSchema(),

135 PKG_DIMS_SCHEMA,

136 ],

137 "mobile_immobile_mass_transfer_rate": [

138 DTypeSchema(np.floating),

139 IndexesSchema(),

140 PKG_DIMS_SCHEMA,

141 ],

142 "decay": [

143 DTypeSchema(np.floating),

144 IndexesSchema(),

145 PKG_DIMS_SCHEMA,

146 ],

147 "decay_sorbed": [

148 DTypeSchema(np.floating),

149 IndexesSchema(),

150 PKG_DIMS_SCHEMA,

151 ],

152 "bulk_density": [

153 DTypeSchema(np.floating),

154 IndexesSchema(),

155 PKG_DIMS_SCHEMA,

156 ],

157 "distribution_coefficient": [

158 DTypeSchema(np.floating),

159 IndexesSchema(),

160 PKG_DIMS_SCHEMA,

161 ],

162 }

163

164 _write_schemata = {

165 "initial_immobile_concentration": [

166 AllValueSchema(">", 0.0),

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

168 ],

169 "immobile_porosity": [

170 AllValueSchema(">=", 0.0),

171 AllValueSchema("<", 1.0),

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

173 ],

174 "mobile_immobile_mass_transfer_rate": [

175 AllValueSchema(">=", 0.0),

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

177 ],

178 "decay": [IdentityNoDataSchema(other="idomain", is_other_notnull=(">", 0))],

179 "decay_sorbed": [

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

181 ],

182 "bulk_density": [

183 AllValueSchema(">", 0.0),

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

185 ],

186 "distribution_coefficient": [

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

188 ],

189 }

190

191 @init_log_decorator()

192 def __init__(

193 self,

194 initial_immobile_concentration,

195 immobile_porosity,

196 mobile_immobile_mass_transfer_rate,

197 decay=None,

198 decay_sorbed=None,

199 bulk_density: Optional[xr.DataArray] = None,

200 distribution_coefficient=None,

201 save_flows: Optional[bool] = None,

202 budgetbinfile: Optional[str] = None,

203 budgetcsvfile: Optional[str] = None,

204 sorption: bool = False,

205 first_order_decay: bool = False,

206 zero_order_decay: bool = False,

207 cimfile: str = "cim.dat",

208 columns: int = 7,

209 width: int = 10,

210 digits: int = 7,

211 format: str = "EXPONENTIAL",

212 validate: bool = True,

213 ):

214 # is True fails on a np.bool_ True.

215 if sorption:

216 if bulk_density is None or distribution_coefficient is None:

217 raise ValueError(

218 "if sorption is active, a bulk density and distribution "

219 "coefficient must be provided.",

220 )

221 if first_order_decay or zero_order_decay:

222 if decay is None:

223 raise ValueError(

224 "if first_order_decay is active or if zero_order_decay is "

225 "active, decay must be provided.",

226 )

227 if sorption:

228 if decay_sorbed is None:

229 raise ValueError(

230 "if first_order_decay is active or if zero_order_decay "

231 "is active, and sorption is active, decay_sorbed must be "

232 "provided.",

233 )

234

235 dict_dataset = {

236 "initial_immobile_concentration": initial_immobile_concentration,

237 "mobile_immobile_mass_transfer_rate": mobile_immobile_mass_transfer_rate,

238 "immobile_porosity": immobile_porosity,

239 "decay": decay,

240 "decay_sorbed": decay_sorbed,

241 "bulk_density": bulk_density,

242 "distribution_coefficient": distribution_coefficient,

243 "save_flows": save_flows,

244 "budgetfile": budgetbinfile,

245 "budgetcsvfile": budgetcsvfile,

246 "sorption": sorption,

247 "first_order_decay": first_order_decay,

248 "zero_order_decay": zero_order_decay,

249 "cimfile": cimfile,

250 "columns ": columns,

251 "width": width,

252 "digits": digits,

253 "format": format,

254 }

255 super().__init__(dict_dataset)

256 self._validate_init_schemata(validate)