MODFLOW 6  version 6.8.0.dev0
USGS Modular Hydrologic Model
MethodSubcellPollock.f90
Go to the documentation of this file.
2  use kindmodule, only: dp, i4b, lgp
3  use errorutilmodule, only: pstop
8  use prtfmimodule, only: prtfmitype
9  use basedismodule, only: disbasetype
10  use cellmodule, only: celltype
11  use constantsmodule, only: dzero, done
12  use domainmodule, only: domaintype
13  use subcellmodule, only: subcelltype
14  use listmodule, only: listtype
19  implicit none
20  private
21  public :: methodsubcellpollocktype
23  public :: calculate_dt
24 
25  !> @brief Rectangular subcell tracking method
27  private
28  real(dp), allocatable, public :: qextl1(:), qextl2(:), qintl(:) !< external and internal subcell flows
29  contains
30  procedure, public :: apply => apply_msp
31  procedure, public :: deallocate
32  procedure, private :: track_subcell
34 
35 contains
36 
37  !> @brief Create a new Pollock's subcell method
38  subroutine create_method_subcell_pollock(method)
39  ! dummy
40  type(methodsubcellpollocktype), pointer :: method
41  ! local
42  type(subcellrecttype), pointer :: subcell
43 
44  allocate (method)
45  call create_subcell_rect(subcell)
46  method%subcell => subcell
47  method%name => method%subcell%type
48  method%delegates = .false.
49  end subroutine create_method_subcell_pollock
50 
51  !> @brief Deallocate the Pollock's subcell method
52  subroutine deallocate (this)
53  class(methodsubcellpollocktype), intent(inout) :: this
54  deallocate (this%name)
55  end subroutine deallocate
56 
57  !> @brief Apply Pollock's method to a rectangular subcell
58  subroutine apply_msp(this, particle, tmax)
59  ! dummy
60  class(methodsubcellpollocktype), intent(inout) :: this
61  type(particletype), pointer, intent(inout) :: particle
62  real(DP), intent(in) :: tmax
63  ! local
64  real(DP) :: x_origin
65  real(DP) :: y_origin
66  real(DP) :: sinrot
67  real(DP) :: cosrot
68 
69  select type (subcell => this%subcell)
70  type is (subcellrecttype)
71  ! Transform particle position into local subcell coordinates,
72  ! track particle across subcell, convert back to model coords
73  ! (sinrot and cosrot should be 0 and 1, respectively, i.e. no
74  ! rotation, also no z translation; only x and y translations,
75  ! since subcell%zOrigin is always 0 -- z is translated once,
76  ! at the cell level, not again here)
77  x_origin = subcell%xOrigin
78  y_origin = subcell%yOrigin
79  sinrot = subcell%sinrot
80  cosrot = subcell%cosrot
81  call particle%transform(x_origin, y_origin)
82 
83  ! Clamp the particle into the subcell in case roundoff in the
84  ! model-to-local coordinate transform (e.g. under grid rotation)
85  ! left it just outside, analogous to the nudge applied to the
86  ! ternary method's barycentric coordinates.
87  particle%x = min(max(particle%x, dzero), subcell%dx)
88  particle%y = min(max(particle%y, dzero), subcell%dy)
89  particle%z = min(max(particle%z, dzero), subcell%dz)
90 
91  call this%track_subcell(subcell, particle, tmax)
92  call particle%transform(x_origin, y_origin, invert=.true.)
93  end select
94  end subroutine apply_msp
95 
96  !> @brief Track a particle across a rectangular subcell using Pollock's method
97  !!
98  !! This subroutine consists partly of code written by
99  !! David W. Pollock of the USGS for MODPATH 7. PRT's
100  !! authors take responsibility for its application in
101  !! this context and for any modifications or errors.
102  !<
103  subroutine track_subcell(this, subcell, particle, tmax)
106  ! dummy
107  class(methodsubcellpollocktype), intent(inout) :: this
108  class(subcellrecttype), intent(in) :: subcell
109  type(particletype), pointer, intent(inout) :: particle
110  real(DP), intent(in) :: tmax
111  ! local
112  real(DP) :: dt, dtexit, texit
113  real(DP) :: t, x, y, z
114  real(DP) :: t0, x0, y0, z0
115  integer(I4B) :: i, exit_face, exit_soln
116  type(linearexitsolutiontype) :: exit_solutions(3)
117  type(linearexitsolutiontype) :: exit_x, exit_y, exit_z
118 
119  t0 = particle%ttrack
120 
121  ! Find exit solution in each direction; also returns the
122  ! local subcell coordinates used, so we don't recompute them
123  call find_exits(particle, subcell, exit_solutions, x0, y0, z0)
124  exit_x = exit_solutions(1)
125  exit_y = exit_solutions(2)
126  exit_z = exit_solutions(3)
127 
128  ! Set solution, face, & travel time
129  exit_soln = pick_exit(exit_solutions)
130  if (exit_soln == 0) then
131  exit_face = 0
132  dtexit = 1.0d+30
133  else
134  exit_face = exit_solutions(exit_soln)%iboundary
135  dtexit = exit_solutions(exit_soln)%dt
136  end if
137  texit = particle%ttrack + dtexit
138 
139  ! Terminate if no valid exit solution was found.
140  ! MP7 is more nuanced in determining what to do
141  ! here. It considers whether this stress period
142  ! is steady state or transient, and whether the
143  ! flow is stationary, in determining whether to
144  ! terminate or allow the particle to survive to
145  ! the next time step. It does not compute paths
146  ! within the subcell even for particles it lets
147  ! remain active under this circumstance, though.
148  ! While we may consider that someday, we simply
149  ! terminate and sidestep the complexity for now.
150  if (all([exit_solutions%status] >= no_exit_stationary)) then
151  call this%terminate(particle, status=term_no_exits_sub)
152  return
153  end if
154 
155  ! Select user tracking times to solve. If this is the first time step
156  ! of the simulation, include all times before it begins; if it is the
157  ! last time step include all times after it ends only if the 'extend'
158  ! option is on, otherwise times in this period and time step only.
159  call this%tracktimes%advance()
160  if (this%tracktimes%any()) then
161  do i = this%tracktimes%selection(1), this%tracktimes%selection(2)
162  t = this%tracktimes%times(i)
163  if (t < particle%ttrack) cycle
164  if (t > texit .or. t > tmax) exit
165  dt = t - t0
166  x = new_x(exit_x%v, exit_x%dvdx, subcell%vx1, subcell%vx2, &
167  dt, x0, subcell%dx, exit_x%status == 1)
168  y = new_x(exit_y%v, exit_y%dvdx, subcell%vy1, subcell%vy2, &
169  dt, y0, subcell%dy, exit_y%status == 1)
170  z = new_x(exit_z%v, exit_z%dvdx, subcell%vz1, subcell%vz2, &
171  dt, z0, subcell%dz, exit_z%status == 1)
172  particle%x = x * subcell%dx
173  particle%y = y * subcell%dy
174  particle%z = z * subcell%dz
175  particle%ttrack = t
176  particle%istatus = active
177  call this%usertime(particle)
178  end do
179  end if
180 
181  ! Computed exit time greater than the maximum time? Set the
182  ! tracking time to tmax and calculate the particle location.
183  if (texit .gt. tmax) then
184  t = tmax
185  dt = t - t0
186  x = new_x(exit_x%v, exit_x%dvdx, subcell%vx1, subcell%vx2, &
187  dt, x0, subcell%dx, exit_x%status == 1)
188  y = new_x(exit_y%v, exit_y%dvdx, subcell%vy1, subcell%vy2, &
189  dt, y0, subcell%dy, exit_y%status == 1)
190  z = new_x(exit_z%v, exit_z%dvdx, subcell%vz1, subcell%vz2, &
191  dt, z0, subcell%dz, exit_z%status == 1)
192  exit_face = 0
193  particle%istatus = active
194  particle%advancing = .false.
195  particle%x = x * subcell%dx
196  particle%y = y * subcell%dy
197  particle%z = z * subcell%dz
198  particle%ttrack = t
199  particle%iboundary(level_subfeature) = exit_face
200  call this%timestep(particle)
201  return
202  end if
203 
204  ! If we get to here, the particle is exiting the subcell.
205  ! Its exit time is less than or equal to the maximum time.
206  ! Set tracking time to the exit time and set exit location.
207  t = texit
208  dt = dtexit
209  if ((exit_face .eq. 1) .or. (exit_face .eq. 2)) then
210  x = dzero
211  y = new_x(exit_y%v, exit_y%dvdx, subcell%vy1, subcell%vy2, &
212  dt, y0, subcell%dy, exit_y%status == 1)
213  z = new_x(exit_z%v, exit_z%dvdx, subcell%vz1, subcell%vz2, &
214  dt, z0, subcell%dz, exit_z%status == 1)
215  if (exit_face .eq. 2) x = done
216  else if ((exit_face .eq. 3) .or. (exit_face .eq. 4)) then
217  x = new_x(exit_x%v, exit_x%dvdx, subcell%vx1, subcell%vx2, dt, &
218  x0, subcell%dx, exit_x%status == 1)
219  y = dzero
220  z = new_x(exit_z%v, exit_z%dvdx, subcell%vz1, subcell%vz2, dt, &
221  z0, subcell%dz, exit_z%status == 1)
222  if (exit_face .eq. 4) y = done
223  else if ((exit_face .eq. 5) .or. (exit_face .eq. 6)) then
224  x = new_x(exit_x%v, exit_x%dvdx, subcell%vx1, subcell%vx2, &
225  dt, x0, subcell%dx, exit_x%status == 1)
226  y = new_x(exit_y%v, exit_y%dvdx, subcell%vy1, subcell%vy2, &
227  dt, y0, subcell%dy, exit_y%status == 1)
228  z = dzero
229  if (exit_face .eq. 6) z = done
230  else
231  print *, "programmer error, invalid exit face", exit_face
232  call pstop(1)
233  end if
234  particle%x = x * subcell%dx
235  particle%y = y * subcell%dy
236  particle%z = z * subcell%dz
237  particle%ttrack = t
238  particle%iboundary(level_subfeature) = exit_face
239  call this%subcellexit(particle)
240 
241  end subroutine track_subcell
242 
243  !> @brief Pick the exit solution with the shortest travel time
244  function pick_exit(exit_solutions) result(exit_soln)
245  type(linearexitsolutiontype), intent(in) :: exit_solutions(3)
246  integer(I4B) :: exit_soln
247  ! local
248  real(dp) :: dtmin
249 
250  exit_soln = 0
251  dtmin = 1.0d+30
252 
253  if (exit_solutions(1)%status < 2) then
254  exit_soln = 1 ! x
255  dtmin = exit_solutions(1)%dt
256  end if
257  if (exit_solutions(2)%status < 2 .and. &
258  exit_solutions(2)%dt < dtmin) then
259  exit_soln = 2 ! y
260  dtmin = exit_solutions(2)%dt
261  end if
262  if (exit_solutions(3)%status < 2 .and. &
263  exit_solutions(3)%dt < dtmin) then
264  exit_soln = 3 ! z
265  end if
266 
267  end function pick_exit
268 
269  !> @brief Compute candidate exit solutions
270  !!
271  !! Also returns the local subcell coordinates used (x0, y0, z0),
272  !! so track_subcell doesn't need to recompute them from the
273  !! particle's position.
274  !<
275  subroutine find_exits(particle, domain, exit_solutions, x0, y0, z0)
276  type(particletype), pointer, intent(inout) :: particle
277  class(domaintype), intent(in) :: domain
278  type(linearexitsolutiontype), intent(out) :: exit_solutions(3)
279  real(DP), intent(out) :: x0, y0, z0
280 
281  select type (domain)
282  type is (subcellrecttype)
283  ! Initial particle location in scaled subcell coordinates
284  x0 = particle%x / domain%dx
285  y0 = particle%y / domain%dy
286  z0 = particle%z / domain%dz
287 
288  ! Calculate exit solutions for each coordinate direction
289  exit_solutions = [ &
290  find_exit(domain%vx1, domain%vx2, domain%dx, x0), &
291  find_exit(domain%vy1, domain%vy2, domain%dy, y0), &
292  find_exit(domain%vz1, domain%vz2, domain%dz, z0) &
293  ]
294 
295  ! Set exit faces
296  if (exit_solutions(1)%v < dzero) then
297  exit_solutions(1)%iboundary = 1
298  else if (exit_solutions(1)%v > dzero) then
299  exit_solutions(1)%iboundary = 2
300  end if
301  if (exit_solutions(2)%v < dzero) then
302  exit_solutions(2)%iboundary = 3
303  else if (exit_solutions(2)%v > dzero) then
304  exit_solutions(2)%iboundary = 4
305  end if
306  if (exit_solutions(3)%v < dzero) then
307  exit_solutions(3)%iboundary = 5
308  else if (exit_solutions(3)%v > dzero) then
309  exit_solutions(3)%iboundary = 6
310  end if
311  end select
312  end subroutine find_exits
313 
314  !> @brief Find an exit solution for one dimension
315  function find_exit(v1, v2, dx, xL) result(solution)
316  ! dummy
317  real(dp), intent(in) :: v1
318  real(dp), intent(in) :: v2
319  real(dp), intent(in) :: dx
320  real(dp), intent(in) :: xl
321  type(linearexitsolutiontype) :: solution
322 
323  solution = linearexitsolutiontype()
324  solution%status = calculate_dt(v1, v2, dx, xl, &
325  solution%v, solution%dvdx, solution%dt)
326  end function find_exit
327 
328  !> @brief Calculate particle travel time to exit and exit status.
329  !!
330  !! This subroutine consists partly of code written by and/or adapted from
331  !! David W. Pollock of the USGS for MODPATH 7. The authors of the present
332  !! code are responsible for its appropriate application in this context
333  !! and for any modifications or errors.
334  !<
335  function calculate_dt(v1, v2, dx, xL, v, dvdx, dt) result(status)
336  ! dummy
337  real(dp) :: v1
338  real(dp) :: v2
339  real(dp) :: dx
340  real(dp) :: xl
341  real(dp) :: v
342  real(dp) :: dvdx
343  real(dp) :: dt
344  ! result
345  integer(I4B) :: status
346  ! local
347  real(dp) :: v2a
348  real(dp) :: v1a
349  real(dp) :: dv
350  real(dp) :: dva
351  real(dp) :: vv
352  real(dp) :: vvv
353  real(dp) :: zro
354  real(dp) :: zrom
355  real(dp) :: x
356  real(dp) :: tol
357  real(dp) :: vr1
358  real(dp) :: vr2
359  real(dp) :: vr
360  real(dp) :: v1v2
361  logical(LGP) :: nooutflow
362 
363  ! Initialize variables.
364  status = -1
365  dt = 1.0d+20
366  v2a = v2
367  if (v2a .lt. dzero) v2a = -v2a
368  v1a = v1
369  if (v1a .lt. dzero) v1a = -v1a
370  dv = v2 - v1
371  dva = dv
372  if (dva .lt. dzero) dva = -dva
373 
374  ! Check for a uniform zero velocity in this direction.
375  ! If so, set status = 2 and return (dt = 1.0d+20).
376  tol = 1.0d-15
377  if ((v2a .lt. tol) .and. (v1a .lt. tol)) then
378  v = dzero
379  dvdx = dzero
380  status = no_exit_stationary
381  return
382  end if
383 
384  ! Check for uniform non-zero velocity in this direction.
385  ! If so, set compute dt using the constant velocity,
386  ! set status = 1 and return.
387  vv = v1a
388  if (v2a .gt. vv) vv = v2a
389  vvv = dva / vv
390  if (vvv .lt. 1.0d-4) then
391  zro = tol
392  zrom = -zro
393  v = v1
394  x = xl * dx
395  if (v1 .gt. zro) dt = (dx - x) / v1
396  if (v1 .lt. zrom) dt = -x / v1
397  dvdx = dzero
398  status = ok_exit_constant
399  return
400  end if
401 
402  ! Velocity has a linear variation.
403  ! Compute velocity corresponding to particle position.
404  dvdx = dv / dx
405  v = (done - xl) * v1 + xl * v2
406 
407  ! If flow is into the cell from both sides there is no outflow.
408  ! In that case, set status = 3 and return.
409  nooutflow = .true.
410  if (v1 .lt. dzero) nooutflow = .false.
411  if (v2 .gt. dzero) nooutflow = .false.
412  if (nooutflow) then
413  status = no_exit_no_outflow
414  return
415  end if
416 
417  ! If there is a divide in the cell for this flow direction, check to
418  ! see if the particle is located exactly on the divide. If it is, move
419  ! it very slightly to get it off the divide. This avoids possible
420  ! numerical problems related to stagnation points.
421  if ((v1 .le. dzero) .and. (v2 .ge. dzero)) then
422  if (abs(v) .le. dzero) then
423  v = 1.0d-20
424  if (v2 .le. dzero) v = -v
425  end if
426  end if
427 
428  ! If there is a flow divide, this check finds out what side of the
429  ! divide the particle is on and sets the value of vr appropriately
430  ! to reflect that location.
431  vr1 = v1 / v
432  vr2 = v2 / v
433  vr = vr1
434  if (vr .le. dzero) then
435  vr = vr2
436  end if
437 
438  ! If the product v1*v2 > 0, the velocity is in the same direction
439  ! throughout the cell (i.e. no flow divide). If so, set the value
440  ! of vr to reflect the appropriate direction.
441  v1v2 = v1 * v2
442  if (v1v2 .gt. dzero) then
443  if (v .gt. dzero) vr = vr2
444  if (v .lt. dzero) vr = vr1
445  end if
446 
447  ! Check if vr is (very close to) zero.
448  ! If so, set status = 2 and return (dt = 1.0d+20).
449  if (dabs(vr) .lt. 1.0d-10) then
450  v = dzero
451  dvdx = dzero
452  status = no_exit_stationary
453  return
454  end if
455 
456  ! Compute travel time to exit face. Return with status = 0.
457  dt = log(vr) / dvdx
458  status = ok_exit
459 
460  end function calculate_dt
461 
462  !> @brief Update a cell-local coordinate based on a time increment.
463  !!
464  !! This subroutine consists partly or entirely of code written by
465  !! David W. Pollock of the USGS for MODPATH 7. The authors of the present
466  !! code are responsible for its appropriate application in this context
467  !! and for any modifications or errors.
468  !<
469  pure function new_x(v, dvdx, v1, v2, dt, x, dx, velocity_profile) result(newx)
470  ! dummy
471  real(dp), intent(in) :: v
472  real(dp), intent(in) :: dvdx
473  real(dp), intent(in) :: v1
474  real(dp), intent(in) :: v2
475  real(dp), intent(in) :: dt
476  real(dp), intent(in) :: x
477  real(dp), intent(in) :: dx
478  logical(LGP), intent(in), optional :: velocity_profile
479  ! result
480  real(dp) :: newx
481  logical(LGP) :: lprofile
482 
483  ! process optional arguments
484  if (present(velocity_profile)) then
485  lprofile = velocity_profile
486  else
487  lprofile = .false.
488  end if
489 
490  ! recompute coordinate
491  newx = x
492  if (lprofile) then
493  newx = newx + (v1 * dt / dx)
494  else if (v .ne. dzero) then
495  newx = newx + (v * (exp(dvdx * dt) - done) / dvdx / dx)
496  end if
497 
498  ! clamp to [0, 1]
499  if (newx .lt. dzero) newx = dzero
500  if (newx .gt. done) newx = done
501 
502  end function new_x
503 
This module contains simulation constants.
Definition: Constants.f90:9
real(dp), parameter dzero
real constant zero
Definition: Constants.f90:65
real(dp), parameter done
real constant 1
Definition: Constants.f90:76
subroutine pstop(status, message)
Stop the program, optionally specifying an error status code.
Definition: ErrorUtil.f90:24
@ ok_exit_constant
exit found, constant velocity
@ ok_exit
exit found using velocity interpolation
@ no_exit_stationary
no exit, zero velocity
@ no_exit_no_outflow
no exit, no outflow
This module defines variable data types.
Definition: kind.f90:8
Particle tracking strategies.
Definition: Method.f90:2
@, public level_subfeature
Definition: Method.f90:41
subroutine find_exits(particle, domain, exit_solutions, x0, y0, z0)
Compute candidate exit solutions.
pure real(dp) function new_x(v, dvdx, v1, v2, dt, x, dx, velocity_profile)
Update a cell-local coordinate based on a time increment.
integer(i4b) function, public calculate_dt(v1, v2, dx, xL, v, dvdx, dt)
Calculate particle travel time to exit and exit status.
type(linearexitsolutiontype) function find_exit(v1, v2, dx, xL)
Find an exit solution for one dimension.
integer(i4b) function pick_exit(exit_solutions)
Pick the exit solution with the shortest travel time.
subroutine track_subcell(this, subcell, particle, tmax)
Track a particle across a rectangular subcell using Pollock's method.
subroutine, public create_method_subcell_pollock(method)
Create a new Pollock's subcell method.
subroutine apply_msp(this, particle, tmax)
Apply Pollock's method to a rectangular subcell.
@, public featexit
particle exited a grid feature
@, public timestep
time step ended
@ term_timeout
terminated at stop time or end of simulation
Definition: Particle.f90:40
@ term_no_exits_sub
terminated in a subcell with no exit face
Definition: Particle.f90:39
subroutine, public create_subcell_rect(subcell)
Create a new rectangular subcell.
Definition: SubcellRect.f90:28
Base type for grid cells of a concrete type. Contains a cell-definition which is information shared b...
Definition: Cell.f90:12
A tracking domain.
Definition: Domain.f90:8
Base type for exit solutions.
Linear velocity interpolation exit solution.
A generic heterogeneous doubly-linked list.
Definition: List.f90:14
Abstract base type for subcell tracking methods.
Particle tracked by the PRT model.
Definition: Particle.f90:64
A subcell of a cell.
Definition: Subcell.f90:10