MODFLOW 6  version 6.9.0.dev0
USGS Modular Hydrologic Model
gwf-lak-implicit.f90
Go to the documentation of this file.
1 submodule(lakmodule) lakmoduleimplicit
2 contains
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 
367 end submodule lakmoduleimplicit
real(dp), pointer, public delt
length of the current time step
Definition: tdis.f90:32