Coverage for C:\src\imod-python\imod\wq\btn.py: 25%

1 import jinja2

2 import numpy as np

3 import scipy.ndimage

4

5 from imod.wq.pkgbase import Package

6

7

8 class BasicTransport(Package):

9 """

10 Handles basic tasks that are required by the entire transport model. Among

11 these tasks are definition of the problem, specification of the boundary and

12 initial conditions, determination of the stepsize, preparation of mass

13 balance information, and printout of the simulation results.

14

15 Parameters

16 ----------

17 icbund: xr.DataArray of int

18 is an integer array specifying the boundary condition type (inactive,

19 constant-concentration, or active) for every model cell. For

20 multi-species simulation, ICBUND defines the boundary condition type

21 shared by all species. Note that different species are allowed to have

22 different constant-concentration conditions through an option in the

23 Source and Sink Mixing Package.

24 ICBUND=0, the cell is an inactive concentration cell for all species.

25 Note that no-flow or "dry" cells are automatically converted into

26 inactive concentration cells. Furthermore, active cells in terms of flow

27 can be treated as inactive concentration cells to minimize the area

28 needed for transport simulation, as long as the solute transport is

29 insignificant near those cells.

30 ICBUND<0, the cell is a constant-concentration cell for all species. The

31 starting concentration of each species remains the same at the cell

32 throughout the simulation. (To define different constantconcentration

33 conditions for different species at the same cell location, refer to the

34 Sink/Source Mixing Package.) Also note that unless explicitly defined as

35 a constant-concentration cell, a constant-head cell in the flow model is

36 not treated as a constantconcentration cell.

37 If ICBUND>0, the cell is an active (variable) concentration cell where

38 the concentration value will be calculated.

39 starting_concentration: float or xr.DataArray of floats

40 is the starting concentration (initial condition) at the beginning of

41 the simulation (unit: ML-3) (SCONC). For multispecies simulation, the

42 starting concentration must be specified for all species, one species at

43 a time.

44 porosity: float, optional

45 is the "effective" porosity of the porous medium in a single porosity

46 system (PRSITY).

47 Default value is 0.35.

48 n_species: int, optional

49 is the total number of chemical species included in the current

50 simulation (NCOMP). For single-species simulation, set n_species = 1.

51 Default value is 1.

52 inactive_concentration: float, optional

53 is the value for indicating an inactive concentration cell (ICBUND=0)

54 (CINACT). Even if it is not anticipated to have inactive cells in the

55 model, a value for inactive_concentration still must be submitted.

56 Default value is 1.0e30

57 minimum_active_thickness: float, optional

58 is the minimum saturated thickness in a cell (THKMIN), expressed as the

59 decimal fraction of the model layer thickness, below which the cell is

60 considered inactive.

61 Default value is 0.01 (i.e., 1% of the model layer thickness).

62 """

63

64 _pkg_id = "btn"

65

66 _mapping = (("icbund", "icbund"), ("dz", "thickness"), ("prsity", "porosity"))

67

68 _template = jinja2.Template(

69 "[btn]\n"

70 " ncomp = {{n_species}}\n" # Number of components

71 " mcomp = {{n_species}}\n" # Number of mobile components

72 " thkmin = {{minimum_active_thickness}}\n"

73 " cinact = {{inactive_concentration}}\n"

74 " {%- for species, layerdict in starting_concentration.items() %}\n"

75 " {%- for layer, value in layerdict.items() %}\n"

76 " sconc_t{{species}}_l{{layer}} = {{value}}\n"

77 " {%- endfor -%}\n"

78 " {%- endfor -%}\n"

79 " {%- for name, dictname in mapping -%}\n"

80 " {%- for layer, value in dicts[dictname].items() %}\n"

81 " {{name}}_l{{layer}} = {{value}}\n"

82 " {%- endfor -%}\n"

83 " {%- endfor -%}\n"

84 )

85

86 def __init__(

87 self,

88 icbund,

89 starting_concentration,

90 porosity=0.35,

91 n_species=1,

92 inactive_concentration=1.0e30,

93 minimum_active_thickness=0.01,

94 ):

95 super().__init__()

96 self["icbund"] = icbund

97 self["starting_concentration"] = starting_concentration

98 self["porosity"] = porosity

99 self["n_species"] = n_species

100 self["inactive_concentration"] = inactive_concentration

101 self["minimum_active_thickness"] = minimum_active_thickness

102

103 def _render(self, directory, nlayer):

104 """

105 Renders part of [btn] section that does not depend on time,

106 and can be inferred without checking the BoundaryConditions.

107

108 Parameters

109 ----------

110 directory : str

111 thickness : xr.DataArray

112 Taken from BasicFlow

113

114 Returns

115 -------

116 rendered : str

117 """

118 d = {}

119 dicts = {}

120 d["mapping"] = self._mapping

121 # Starting concentration also includes a species, and can't be written

122 # in the same way as the other variables; _T? in the runfile

123 if "species" in self.dataset["starting_concentration"].coords:

124 starting_concentration = {}

125

126 for i, species in enumerate(

127 self.dataset["starting_concentration"]["species"].values

128 ):

129 da = self.dataset["starting_concentration"].sel(species=species)

130 starting_concentration[i + 1] = self._compose_values_layer(

131 "starting_concentration", directory, nlayer=nlayer, da=da

132 )

133

134 d["starting_concentration"] = starting_concentration

135 else:

136 d["starting_concentration"] = {

137 1: self._compose_values_layer(

138 "starting_concentration", directory, nlayer=nlayer

139 )

140 }

141

142 # Collect which entries are complex (multi-dim)

143 data_vars = [t[1] for t in self._mapping]

144 for varname in self.dataset.data_vars.keys():

145 if varname == "starting_concentration":

146 continue # skip it, as mentioned above

147 if varname in data_vars: # multi-dim entry

148 dicts[varname] = self._compose_values_layer(

149 varname, directory, nlayer=nlayer

150 )

151 else: # simple entry, just get the scalar value

152 d[varname] = self.dataset[varname].values

153

154 # Add these from the outside, thickness from BasicFlow

155 # layer_type from LayerPropertyFlow

156 dicts["thickness"] = self._compose_values_layer(

157 "thickness", directory, nlayer=nlayer, da=self.dataset.thickness

158 )

159 d["dicts"] = dicts

160 return self._template.render(d)

161

162 def _pkgcheck(self, ibound=None):

163 to_check = [

164 "starting_concentration",

165 "porosity",

166 "n_species",

167 "minimum_active_thickness",

168 ]

169 self._check_positive(to_check)

170

171 active_cells = self.dataset["icbund"] != 0

172 if (active_cells & np.isnan(self.dataset["starting_concentration"])).any():

173 raise ValueError(

174 f"Active cells in icbund may not have a nan value in starting_concentration in {self}"

175 )

176

177 _, nlabels = scipy.ndimage.label(active_cells.values)

178

179

180# if nlabels > 1:

181# raise ValueError(

182# f"{nlabels} disconnected model domain detected in the icbund in {self}"

183# )