Coverage for C:\src\imod-python\imod\wq\btn.py: 25%
44 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-08 10:26 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-08 10:26 +0200
1import jinja2
2import numpy as np
3import scipy.ndimage
5from imod.wq.pkgbase import Package
8class 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.
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 """
64 _pkg_id = "btn"
66 _mapping = (("icbund", "icbund"), ("dz", "thickness"), ("prsity", "porosity"))
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 )
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
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.
108 Parameters
109 ----------
110 directory : str
111 thickness : xr.DataArray
112 Taken from BasicFlow
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 = {}
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 )
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 }
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
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)
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)
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 )
177 _, nlabels = scipy.ndimage.label(active_cells.values)
180# if nlabels > 1:
181# raise ValueError(
182# f"{nlabels} disconnected model domain detected in the icbund in {self}"
183# )