MODFLOW 6  version 6.8.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 substitution fallback (lak_set_fallback)
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) :: lfallback
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  !
102  ! -- provide the outlet outflow to the mover (matches the mover-provider
103  ! accumulation done at the end of the legacy lak_solve)
104  if (this%imover == 1) then
105  do n = 1, this%noutlets
106  call this%pakmvrobj%accumulate_qformvr(n, -this%simoutrate(n))
107  end do
108  end if
109  end if
110  !
111  ! -- development option: force every active lake onto the fallback (used to
112  ! test the fallback assembly against the legacy formulation)
113  if (this%iforcefb /= 0) then
114  do n = 1, this%nlakes
115  if (this%iboundpak(n) > 0) this%ifallback(n) = 1
116  end do
117  end if
118  !
119  ! -- solve the stage of any lake assigned to the substitution fallback
120  ! against the current groundwater heads. A fallback lake is then
121  ! assembled below like a constant-stage lake at its solved stage, so a
122  ! weakly connected or disconnected lake -- which is poorly conditioned as
123  ! a matrix unknown -- is handled by the robust 1D substitution instead.
124  lfallback = .false.
125  do n = 1, this%nlakes
126  if (this%ifallback(n) /= 0) then
127  lfallback = .true.
128  exit
129  end if
130  end do
131  if (lfallback) then
132  call this%lak_solve(only_fallback=.true.)
133  end if
134  !
135  ipos = 0
136  do n = 1, this%nlakes
137  iloc = this%idxlocnode(n)
138  hlak = this%xnewpak(n)
139  if (this%ifallback(n) /= 0 .and. this%iboundpak(n) > 0) then
140  !
141  ! -- fallback lake: its stage was solved by substitution above. Hold the
142  ! lake row at that stage (unit diagonal) and add the resulting
143  ! lake-aquifer exchange (this%hcof/this%rhs, exactly as the legacy
144  ! formulation does) to the connected gwf cells.
145  call matrix_sln%add_value_pos(this%idxdiag(n), done)
146  rhs(iloc) = hlak
147  do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
148  ipos = ipos + 1
149  igwfnode = this%cellid(j)
150  if (this%ibound(igwfnode) < 1) cycle
151  call matrix_sln%add_value_pos(this%idxsymdglo(ipos), this%hcof(j))
152  rhs(igwfnode) = rhs(igwfnode) + this%rhs(j)
153  end do
154  else if (this%iboundpak(n) > 0) then
155  !
156  ! -- active lake: linearize the non-seepage budget B(stage) about the
157  ! current stage with a finite difference. dB/dstage goes on the
158  ! lake-row diagonal and (dB/dstage*stage - B) goes on the rhs.
159  call this%lak_budget_nogwf(n, hlak, b0)
160  call this%lak_budget_nogwf(n, hlak + this%delh, b1)
161  dbds = (b1 - b0) / this%delh
162  call matrix_sln%add_value_pos(this%idxdiag(n), dbds)
163  rhs(iloc) = rhs(iloc) + dbds * hlak - b0
164  adiag = dbds
165  !
166  do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
167  ipos = ipos + 1
168  igwfnode = this%cellid(j)
169  if (this%ibound(igwfnode) < 1) cycle
170  head = this%xnew(igwfnode)
171  !
172  ! -- lakebed seepage and its derivatives. The lake stage and the
173  ! connected-cell head are each kept at or above the lake bottom,
174  ! the same wet/dry cutoff as the default formulation. A wet
175  ! connection (dps = dph = 1) couples both stage and head; a perched
176  ! connection (dph = 0) drops the head coupling but keeps the stage
177  ! coupling (dqds), so the lake-stage diagonal stays non-zero and the
178  ! perched leakage still responds to stage.
179  call this%lak_calculate_conn_exchange_deriv(n, j, hlak, head, flow, &
180  dqds, dqdh)
181  call matrix_sln%add_value_pos(this%idxdiag(n), dqds)
182  call matrix_sln%add_value_pos(this%idxoffdglo(ipos), dqdh)
183  call matrix_sln%add_value_pos(this%idxsymdglo(ipos), -dqdh)
184  call matrix_sln%add_value_pos(this%idxsymoffdglo(ipos), -dqds)
185  rhs(iloc) = rhs(iloc) + dqds * hlak + dqdh * head - flow
186  rhs(igwfnode) = rhs(igwfnode) - dqdh * head - dqds * hlak + flow
187  adiag = adiag + dqds
188  end do
189  !
190  ! -- keep the row solvable when its diagonal is still (near) zero -- a
191  ! lake with no wet connections and no storage. Add a small diagonal
192  ! term and a matching rhs term so the row holds the stage at its
193  ! current value; the contribution is exactly zero at convergence.
194  csum = dzero
195  do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
196  csum = csum + this%satcond(j)
197  end do
198  deps = dem9 * csum + dprec
199  if (abs(adiag) < deps) then
200  call matrix_sln%add_value_pos(this%idxdiag(n), -deps)
201  rhs(iloc) = rhs(iloc) - deps * hlak
202  end if
203  else if (this%iboundpak(n) < 0) then
204  !
205  ! -- constant-stage lake: hold the lake row at the specified stage and
206  ! add the seepage to the gwf cells as a constant exchange evaluated at
207  ! the fixed stage
208  call matrix_sln%add_value_pos(this%idxdiag(n), done)
209  rhs(iloc) = hlak
210  do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
211  ipos = ipos + 1
212  igwfnode = this%cellid(j)
213  if (this%ibound(igwfnode) < 1) cycle
214  head = this%xnew(igwfnode)
215  call this%lak_calculate_conn_exchange(n, j, hlak, head, flow, &
216  gwfhcof, gwfrhs)
217  call matrix_sln%add_value_pos(this%idxsymdglo(ipos), gwfhcof)
218  rhs(igwfnode) = rhs(igwfnode) + gwfrhs
219  end do
220  else
221  !
222  ! -- inactive lake (iboundpak == 0): the lake is absent and exchanges no
223  ! water with the aquifer. Disconnect the lake row (its global ibound
224  ! is 0, so the solver drops the equation) with a unit diagonal and add
225  ! NO seepage to the gwf cells. Only advance ipos to keep the
226  ! connection index aligned. rhs is left at zero so the large
227  ! placeholder value used for an inactive stage is not added to the
228  ! linear system.
229  call matrix_sln%add_value_pos(this%idxdiag(n), done)
230  rhs(iloc) = dzero
231  do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
232  ipos = ipos + 1
233  end do
234  end if
235  end do
236  end procedure lak_fc_implicit
237 
238  !> @brief Switch a stalled IMPLICIT lake to the substitution fallback
239  !!
240  !! Under the IMPLICIT option a weakly connected or disconnected lake (one with
241  !! a small lakebed conductance) gives a poorly conditioned lake-stage equation
242  !! that the coupled solver may be unable to advance. Such a lake is the
243  !! convergence bottleneck: its stage keeps changing by much more than its
244  !! connected aquifer heads, which have settled. A well-connected lake, by
245  !! contrast, changes its stage in step with its aquifer. When the stage change
246  !! exceeds the connected-head change by a large factor for several outer
247  !! iterations (see the inline criterion), the lake is flagged (ifallback), and
248  !! lak_fc_implicit then solves and assembles it with the robust substitution
249  !! solver instead. A lake that changes in step with its aquifer is never
250  !! switched. The flag and the per-lake tracking are cleared at the start of
251  !! each time step, so a lake switched in one time step is reconsidered for the
252  !! implicit formulation in the next, where transient storage or different
253  !! stresses may make its stage equation better conditioned.
254  !<
255  module procedure lak_set_fallback
256  ! -- local
257  integer(I4B) :: n, j, igwfnode
258  real(DP) :: dstage, dhead
259  ! -- a lake is treated as the convergence bottleneck when its stage change
260  ! exceeds headratio times the largest change in its connected aquifer
261  ! heads; more than nstuckmax such consecutive outer iterations switch it to
262  ! the fallback. A well-connected lake converges in lockstep with its aquifer
263  ! (ratio near one) and is never switched; a weakly connected or disconnected
264  ! lake keeps thrashing while its connected heads settle (ratio grows without
265  ! bound), so it is switched early -- before the heads commit to the stalled
266  ! configuration -- which the fallback then resolves quickly.
267  real(DP), parameter :: headratio = dten
268  integer(I4B), parameter :: nstuckmax = 5
269  !
270  if (this%iimplicit == 0) return
271  !
272  ! -- at the start of a time step, snapshot the connected heads and reset the
273  ! bottleneck counts. Only snapshot active cells; an inactive cell holds a
274  ! placeholder head that would otherwise distort the head-change comparison
275  ! if the cell later becomes active.
276  if (kiter == 1) then
277  do n = 1, this%nlakes
278  this%ifallback(n) = 0
279  this%nstuck(n) = 0
280  do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
281  igwfnode = this%cellid(j)
282  if (this%ibound(igwfnode) >= 1) then
283  this%holdconn(j) = this%xnew(igwfnode)
284  end if
285  end do
286  end do
287  return
288  end if
289  !
290  ! -- only evaluate while the model has not converged
291  if (icnvgmod /= 0) return
292  do n = 1, this%nlakes
293  if (this%iboundpak(n) < 1) cycle
294  !
295  ! -- stage change over this outer iteration and the largest change in the
296  ! connected-cell heads, then advance the snapshot
297  dstage = abs(this%xnewpak(n) - this%s0(n))
298  dhead = dzero
299  do j = this%idxlakeconn(n), this%idxlakeconn(n + 1) - 1
300  igwfnode = this%cellid(j)
301  if (this%ibound(igwfnode) >= 1) then
302  dhead = max(dhead, abs(this%xnew(igwfnode) - this%holdconn(j)))
303  this%holdconn(j) = this%xnew(igwfnode)
304  end if
305  end do
306  if (this%ifallback(n) /= 0) cycle
307  !
308  ! -- count outer iterations in which the unconverged stage is changing much
309  ! faster than its (settling) aquifer; switch to the fallback after enough
310  if (dstage > this%dmaxchg .and. dstage > headratio * dhead) then
311  this%nstuck(n) = this%nstuck(n) + 1
312  else
313  this%nstuck(n) = 0
314  end if
315  if (this%nstuck(n) > nstuckmax) then
316  this%ifallback(n) = 1
317  end if
318  end do
319  end procedure lak_set_fallback
320 
321  !> @brief Warn when a fallback lake still prevents IMPLICIT convergence
322  !!
323  !! Called on the last outer iteration of a solution that did not converge. By
324  !! this point any stalled lake has already been switched to the substitution
325  !! fallback (lak_set_fallback). If at least one lake is on the fallback and the
326  !! solution still did not converge, the implicit formulation needed more outer
327  !! iterations than were allowed; a one-time warning reports this and points the
328  !! user to the remedies, including the default formulation, which converges
329  !! this kind of lake in far fewer outer iterations. No warning is issued when
330  !! no lake was switched, because then the lake is not the cause of the failure.
331  !<
332  module procedure lak_check_disconnected
333  ! -- local
334  character(len=LINELENGTH) :: warnmsg
335  integer(I4B) :: n
336  logical(LGP) :: hasfallback
337  !
338  ! -- only the implicit formulation couples the stage to the solver
339  if (this%iimplicit == 0) return
340  !
341  ! -- only warn if a lake was switched to the fallback (otherwise the lake is
342  ! not the cause of the non-convergence)
343  hasfallback = .false.
344  do n = 1, this%nlakes
345  if (this%ifallback(n) /= 0) then
346  hasfallback = .true.
347  exit
348  end if
349  end do
350  if (.not. hasfallback) return
351  !
352  ! -- warn with remedies; deduplicated so it is issued at most once
353  write (warnmsg, '(a)') &
354  "IMPLICIT lake option in package '"//trim(this%packName)//"' did not "// &
355  "converge even after switching a stalled lake to the substitution "// &
356  "solver. The default solver needs fewer outer iterations for such a "// &
357  "lake; consider omitting IMPLICIT, adding an outlet, increasing the "// &
358  "leakance, or running transient."
359  call store_warning(warnmsg, substring=warnmsg(:40))
360  end procedure lak_check_disconnected
361 
362 end submodule lakmoduleimplicit
real(dp), pointer, public delt
length of the current time step
Definition: tdis.f90:32