MODFLOW 6 version 6.9.0.dev0
USGS Modular Hydrologic Model
Loading...
Searching...
No Matches
gwf-lak-implicit.f90
Go to the documentation of this file.
1submodule(lakmodule) lakmoduleimplicit
2contains
3
4 !> @brief Sum the non-seepage lake budget terms at a given stage
5 !!
6 !! Returns B(stage), the sum of every lake-budget term except the groundwater
7 !! seepage: rainfall + runoff + specified and external inflow + outlet inflow
8 !! and outflow - evaporation - withdrawal + transient storage. The stage-driven
9 !! losses are ramped to zero as the stage approaches the lake bottom (over the
10 !! surfdep interval) so a drying lake cannot remove more water than it holds.
11 !! lak_fc_implicit calls this routine at two stages to finite-difference the
12 !! lake-row diagonal and right-hand side.
13 !<
14 module procedure lak_budget_nogwf
15 ! -- modules
16 use tdismodule, only: delt
17 ! -- local
18 real(DP) :: ra, ro, qin, ex, surfin, sa, v0, v1, qout, sf
19 !
20 call this%lak_calculate_rainfall(n, stage, ra)
21 call this%lak_calculate_runoff(n, ro)
22 call this%lak_calculate_inflow(n, qin)
23 call this%lak_calculate_external(n, ex)
24 call this%lak_calculate_outlet_inflow(n, surfin)
25 call this%lak_outlet_outflow_rate(n, stage, qout)
26 call this%lak_calculate_sarea(n, stage, sa)
27 !
28 ! -- smooth flow-limiting: ramp the stage-driven losses (evaporation,
29 ! withdrawal, outlet outflow) to zero as the lake approaches its bottom
30 ! over the surfdep interval, so a drying lake cannot extract more water
31 ! than it holds. With surfdep = 0 (or a lake well above its bottom) the
32 ! factor is 1 and the losses are unlimited (matching the legacy result
33 ! whenever the lake is not water-limited).
34 sf = done
35 if (this%surfdep > dzero) then
36 sf = squadraticsaturation(this%lakebot(n) + this%surfdep, &
37 this%lakebot(n), stage)
38 end if
39 !
40 ! -- evaporation and withdrawal are losses (positive demand); qout is the
41 ! (negative) outlet outflow; surfin is routed inflow from upstream outlets
42 ! (lagged at the previous iterate's stage via simoutrate)
43 b = ra + ro + qin + ex + surfin &
44 + (qout - this%evaporation(n) * sa - this%withdrawal(n)) * sf
45 !
46 ! -- transient storage change (v0 - v1)/delt
47 if (this%gwfiss /= 1) then
48 call this%lak_calculate_vol(n, this%xoldpak(n), v0)
49 call this%lak_calculate_vol(n, stage, v1)
50 b = b + (v0 - v1) / delt
51 end if
52 end procedure lak_budget_nogwf
53
54 !> @brief Assemble the lake equations into the groundwater flow matrix
55 !!
56 !! Implicit formulation. For each active lake the water-balance equation is
57 !! assembled directly into the matrix:
58 !! - the lakebed seepage to each connected cell is a conductance coupling
59 !! between the lake-stage row and the cell row. In the seepage, the lake
60 !! stage and the connected-cell head are each kept at or above the lake
61 !! bottom (the same wet/dry cutoff as the default formulation), so a wet
62 !! connection couples both stage and head, while a perched connection keeps
63 !! the stage coupling (a non-zero lake-stage diagonal) but drops the head
64 !! coupling, and
65 !! - the remaining (non-seepage) budget terms -- rainfall, evaporation,
66 !! withdrawal, runoff, specified and external inflow, outlet inflow and
67 !! outflow, and transient storage -- are linearized in stage with a finite
68 !! difference (lak_budget_nogwf) and added to the diagonal and right-hand
69 !! side. This captures the stage-dependent surface area and the storage
70 !! term.
71 !! The implicit lake-aquifer coupling is asymmetric (a perched connection
72 !! couples the lake to the cell but not the reverse), so the coefficient matrix
73 !! is asymmetric and the BICGSTAB linear acceleration is required.
74 !! Lake-to-lake outlet routing is lagged one outer iteration through simoutrate.
75 !! A constant-stage lake holds its row at the specified stage; an inactive lake
76 !! is disconnected from the aquifer. A lake row whose diagonal is still (near)
77 !! zero (no connections and no storage) is kept solvable with a small diagonal
78 !! term that holds the stage at its current value and becomes zero at
79 !! convergence. A lake flagged for the legacy solver (lak_set_legacy)
80 !! is instead solved by the default substitution solver and assembled like a
81 !! constant-stage lake at that solved stage.
82 !<
83 module procedure lak_fc_implicit
84 ! -- local
85 integer(I4B) :: n, j, ipos, iloc, igwfnode
86 logical(LGP) :: llegacy
87 real(DP) :: hlak, head, flow, gwfhcof, gwfrhs
88 real(DP) :: b0, b1, dbds, avail, sout, csum, deps, adiag
89 real(DP) :: dqds, dqdh
90 !
91 ! -- update simoutrate at the current stages so the routed outlet inflow to
92 ! downstream lakes (lak_calculate_outlet_inflow, read in lak_budget_nogwf)
93 ! reflects the latest upstream stages (lagged one outer iteration). The
94 ! outlet outflow sink itself is linearized per lake in lak_budget_nogwf.
95 if (this%noutlets > 0) then
96 do n = 1, this%nlakes
97 if (this%iboundpak(n) < 1) cycle
98 avail = dep20
99 call this%lak_calculate_outlet_outflow(n, this%xnewpak(n), avail, sout)
100 end do
101 end if
102 !
103 ! -- development option: force every active lake onto the legacy solver (used
104 ! to test that assembly against the legacy formulation)
105 if (this%iforceleg /= 0) then
106 do n = 1, this%nlakes
107 if (this%iboundpak(n) > 0) this%ilegacy(n) = 1
108 end do
109 end if
110 n = this%iforceleglak
111 if (n > 0) then
112 if (this%iboundpak(n) > 0) this%ilegacy(n) = 1
113 end if
114 !
115 ! -- solve the stage of any lake assigned to the legacy solver against the
116 ! current groundwater heads. Such a lake is then
117 ! assembled below like a constant-stage lake at its solved stage, so a
118 ! weakly connected or disconnected lake -- which is poorly conditioned as
119 ! a matrix unknown -- is handled by the robust 1D substitution instead.
120 llegacy = .false.
121 do n = 1, this%nlakes
122 if (this%ilegacy(n) /= 0) then
123 llegacy = .true.
124 exit
125 end if
126 end do
127 if (llegacy) then
128 call this%lak_solve(only_legacy=.true.)
129 end if
130 !
131 ! -- provide the outlet outflow to the mover, once, now that simoutrate is
132 ! final for the implicit lakes and for any lake on the legacy solver
133 ! (matches the mover-provider accumulation at the end of lak_solve)
134 if (this%noutlets > 0 .and. this%imover == 1) then
135 do n = 1, this%noutlets
136 call this%pakmvrobj%accumulate_qformvr(n, -this%simoutrate(n))
137 end do
138 end if
139 !
140 ipos = 0
141 do n = 1, this%nlakes
142 iloc = this%idxlocnode(n)
143 hlak = this%xnewpak(n)
144 if (this%ilegacy(n) /= 0 .and. this%iboundpak(n) > 0) then
145 !
146 ! -- legacy-solved lake: its stage was solved by substitution above. Hold the
147 ! lake row at that stage (unit diagonal) and add the resulting
148 ! lake-aquifer exchange (this%hcof/this%rhs, exactly as the legacy
149 ! formulation does) to the connected gwf cells.
150 call matrix_sln%add_value_pos(this%idxdiag(n), done)
151 rhs(iloc) = hlak
152 do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
153 ipos = ipos + 1
154 igwfnode = this%gwfconn(j)
155 if (this%ibound(igwfnode) < 1) cycle
156 call matrix_sln%add_value_pos(this%idxsymdglo(ipos), this%hcof(j))
157 rhs(igwfnode) = rhs(igwfnode) + this%rhs(j)
158 end do
159 else if (this%iboundpak(n) > 0) then
160 !
161 ! -- active lake: linearize the non-seepage budget B(stage) about the
162 ! current stage with a finite difference. dB/dstage goes on the
163 ! lake-row diagonal and (dB/dstage*stage - B) goes on the rhs.
164 call this%lak_budget_nogwf(n, hlak, b0)
165 call this%lak_budget_nogwf(n, hlak + this%delh, b1)
166 dbds = (b1 - b0) / this%delh
167 call matrix_sln%add_value_pos(this%idxdiag(n), dbds)
168 rhs(iloc) = rhs(iloc) + dbds * hlak - b0
169 adiag = dbds
170 !
171 do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
172 ipos = ipos + 1
173 igwfnode = this%gwfconn(j)
174 if (this%ibound(igwfnode) < 1) cycle
175 head = this%xnew(igwfnode)
176 !
177 ! -- lakebed seepage and its derivatives. The lake stage and the
178 ! connected-cell head are each kept at or above the lake bottom,
179 ! the same wet/dry cutoff as the default formulation. A wet
180 ! connection (dps = dph = 1) couples both stage and head; a perched
181 ! connection (dph = 0) drops the head coupling but keeps the stage
182 ! coupling (dqds), so the lake-stage diagonal stays non-zero and the
183 ! perched leakage still responds to stage.
184 call this%lak_calculate_conn_exchange_deriv(n, j, hlak, head, flow, &
185 dqds, dqdh)
186 call matrix_sln%add_value_pos(this%idxdiag(n), dqds)
187 call matrix_sln%add_value_pos(this%idxoffdglo(ipos), dqdh)
188 call matrix_sln%add_value_pos(this%idxsymdglo(ipos), -dqdh)
189 call matrix_sln%add_value_pos(this%idxsymoffdglo(ipos), -dqds)
190 rhs(iloc) = rhs(iloc) + dqds * hlak + dqdh * head - flow
191 rhs(igwfnode) = rhs(igwfnode) - dqdh * head - dqds * hlak + flow
192 adiag = adiag + dqds
193 end do
194 !
195 ! -- keep the row solvable when its diagonal is still (near) zero -- a
196 ! lake with no wet connections and no storage. Add a small diagonal
197 ! term and a matching rhs term so the row holds the stage at its
198 ! current value; the contribution is exactly zero at convergence.
199 csum = dzero
200 do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
201 csum = csum + this%satcond(j)
202 end do
203 deps = dem9 * csum + dprec
204 if (abs(adiag) < deps) then
205 call matrix_sln%add_value_pos(this%idxdiag(n), -deps)
206 rhs(iloc) = rhs(iloc) - deps * hlak
207 end if
208 else if (this%iboundpak(n) < 0) then
209 !
210 ! -- constant-stage lake: hold the lake row at the specified stage and
211 ! add the seepage to the gwf cells as a constant exchange evaluated at
212 ! the fixed stage
213 call matrix_sln%add_value_pos(this%idxdiag(n), done)
214 rhs(iloc) = hlak
215 do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
216 ipos = ipos + 1
217 igwfnode = this%gwfconn(j)
218 if (this%ibound(igwfnode) < 1) cycle
219 head = this%xnew(igwfnode)
220 call this%lak_calculate_conn_exchange(n, j, hlak, head, flow, &
221 gwfhcof, gwfrhs)
222 call matrix_sln%add_value_pos(this%idxsymdglo(ipos), gwfhcof)
223 rhs(igwfnode) = rhs(igwfnode) + gwfrhs
224 end do
225 else
226 !
227 ! -- inactive lake (iboundpak == 0): the lake is absent and exchanges no
228 ! water with the aquifer. Disconnect the lake row (its global ibound
229 ! is 0, so the solver drops the equation) with a unit diagonal and add
230 ! NO seepage to the gwf cells. Only advance ipos to keep the
231 ! connection index aligned. rhs is left at zero so the large
232 ! placeholder value used for an inactive stage is not added to the
233 ! linear system.
234 call matrix_sln%add_value_pos(this%idxdiag(n), done)
235 rhs(iloc) = dzero
236 do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
237 ipos = ipos + 1
238 end do
239 end if
240 end do
241 end procedure lak_fc_implicit
242
243 !> @brief Switch a stalled IMPLICIT lake back to the legacy solver
244 !!
245 !! Under the IMPLICIT option a weakly connected or disconnected lake (one with
246 !! a small lakebed conductance) gives a poorly conditioned lake-stage equation
247 !! that the coupled solver may be unable to advance. Such a lake is the
248 !! convergence bottleneck: its stage keeps changing by much more than its
249 !! connected aquifer heads, which have settled. A well-connected lake, by
250 !! contrast, changes its stage in step with its aquifer. When the stage change
251 !! exceeds the connected-head change by a large factor for several outer
252 !! iterations (see the inline criterion), the lake is flagged (ilegacy), and
253 !! lak_fc_implicit then solves and assembles it with the robust substitution
254 !! solver instead. A lake that changes in step with its aquifer is never
255 !! switched. The flag and the per-lake tracking are cleared at the start of
256 !! each time step, so a lake switched in one time step is reconsidered for the
257 !! implicit formulation in the next, where transient storage or different
258 !! stresses may make its stage equation better conditioned.
259 !<
260 module procedure lak_set_legacy
261 ! -- local
262 integer(I4B) :: n, j, igwfnode
263 real(DP) :: dstage, dhead
264 ! -- a lake is treated as the convergence bottleneck when its stage change
265 ! exceeds headratio times the largest change in its connected aquifer
266 ! heads; more than nstuckmax such consecutive outer iterations switch it to
267 ! the legacy solver. A well-connected lake converges in lockstep with its aquifer
268 ! (ratio near one) and is never switched; a weakly connected or disconnected
269 ! lake keeps thrashing while its connected heads settle (ratio grows without
270 ! bound), so it is switched early -- before the heads commit to the stalled
271 ! configuration -- which the legacy solver then resolves quickly.
272 real(DP), parameter :: headratio = dten
273 integer(I4B), parameter :: nstuckmax = 5
274 !
275 if (this%iimplicit == 0) return
276 !
277 ! -- at the start of a time step, snapshot the connected heads and reset the
278 ! bottleneck counts. Only snapshot active cells; an inactive cell holds a
279 ! placeholder head that would otherwise distort the head-change comparison
280 ! if the cell later becomes active.
281 if (kiter == 1) then
282 do n = 1, this%nlakes
283 this%ilegacy(n) = 0
284 this%nstuck(n) = 0
285 do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
286 igwfnode = this%gwfconn(j)
287 if (this%ibound(igwfnode) >= 1) then
288 this%holdconn(j) = this%xnew(igwfnode)
289 end if
290 end do
291 end do
292 return
293 end if
294 !
295 ! -- only evaluate while the model has not converged
296 if (icnvgmod /= 0) return
297 do n = 1, this%nlakes
298 if (this%iboundpak(n) < 1) cycle
299 !
300 ! -- stage change over this outer iteration and the largest change in the
301 ! connected-cell heads, then advance the snapshot
302 dstage = abs(this%xnewpak(n) - this%s0(n))
303 dhead = dzero
304 do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
305 igwfnode = this%gwfconn(j)
306 if (this%ibound(igwfnode) >= 1) then
307 dhead = max(dhead, abs(this%xnew(igwfnode) - this%holdconn(j)))
308 this%holdconn(j) = this%xnew(igwfnode)
309 end if
310 end do
311 if (this%ilegacy(n) /= 0) cycle
312 !
313 ! -- count outer iterations in which the unconverged stage is changing much
314 ! faster than its (settling) aquifer; switch to the legacy solver after enough
315 if (dstage > this%dmaxchg .and. dstage > headratio * dhead) then
316 this%nstuck(n) = this%nstuck(n) + 1
317 else
318 this%nstuck(n) = 0
319 end if
320 if (this%nstuck(n) > nstuckmax) then
321 this%ilegacy(n) = 1
322 end if
323 end do
324 end procedure lak_set_legacy
325
326 !> @brief Warn when a legacy-solved lake still prevents IMPLICIT convergence
327 !!
328 !! Called on the last outer iteration of a solution that did not converge. By
329 !! this point any stalled lake has already been switched to the legacy
330 !! substitution solver (lak_set_legacy). If at least one lake is on it and the
331 !! solution still did not converge, the implicit formulation needed more outer
332 !! iterations than were allowed; a one-time warning reports this and points the
333 !! user to the remedies, including the default formulation, which converges
334 !! this kind of lake in far fewer outer iterations. No warning is issued when
335 !! no lake was switched, because then the lake is not the cause of the failure.
336 !<
337 module procedure lak_check_disconnected
338 ! -- local
339 character(len=LINELENGTH) :: warnmsg
340 integer(I4B) :: n
341 logical(LGP) :: haslegacy
342 !
343 ! -- only the implicit formulation couples the stage to the solver
344 if (this%iimplicit == 0) return
345 !
346 ! -- only warn if a lake was switched to the legacy solver (otherwise the lake is
347 ! not the cause of the non-convergence)
348 haslegacy = .false.
349 do n = 1, this%nlakes
350 if (this%ilegacy(n) /= 0) then
351 haslegacy = .true.
352 exit
353 end if
354 end do
355 if (.not. haslegacy) return
356 !
357 ! -- warn with remedies; deduplicated so it is issued at most once
358 write (warnmsg, '(a)') &
359 "IMPLICIT lake option in package '"//trim(this%packName)//"' did not "// &
360 "converge even after switching a stalled lake to the substitution "// &
361 "solver. The default solver needs fewer outer iterations for such a "// &
362 "lake; consider omitting IMPLICIT, adding an outlet, increasing the "// &
363 "leakance, or running transient."
364 call store_warning(warnmsg, substring=warnmsg(:40))
365 end procedure lak_check_disconnected
366
367end submodule lakmoduleimplicit
real(dp), pointer, public delt
length of the current time step
Definition tdis.f90:32