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) :: z_origin
67  real(DP) :: sinrot
68  real(DP) :: cosrot
69 
70  select type (subcell => this%subcell)
71  type is (subcellrecttype)
72  ! Transform particle position into local subcell coordinates,
73  ! track particle across subcell, convert back to model coords
74  ! (sinrot and cosrot should be 0 and 1, respectively, i.e. no
75  ! rotation, also no z translation; only x and y translations)
76  x_origin = subcell%xOrigin
77  y_origin = subcell%yOrigin
78  z_origin = subcell%zOrigin
79  sinrot = subcell%sinrot
80  cosrot = subcell%cosrot
81  call particle%transform(x_origin, y_origin)
82  call this%track_subcell(subcell, particle, tmax)
83  call particle%transform(x_origin, y_origin, invert=.true.)
84  end select
85  end subroutine apply_msp
86 
87  !> @brief Track a particle across a rectangular subcell using Pollock's method
88  !!
89  !! This subroutine consists partly of code written by
90  !! David W. Pollock of the USGS for MODPATH 7. PRT's
91  !! authors take responsibility for its application in
92  !! this context and for any modifications or errors.
93  !<
94  subroutine track_subcell(this, subcell, particle, tmax)
97  ! dummy
98  class(methodsubcellpollocktype), intent(inout) :: this
99  class(subcellrecttype), intent(in) :: subcell
100  type(particletype), pointer, intent(inout) :: particle
101  real(DP), intent(in) :: tmax
102  ! local
103  real(DP) :: dt, dtexit, texit
104  real(DP) :: t, x, y, z
105  real(DP) :: t0, x0, y0, z0
106  integer(I4B) :: i, exit_face, exit_soln
107  type(linearexitsolutiontype) :: exit_solutions(3)
108  type(linearexitsolutiontype) :: exit_x, exit_y, exit_z
109 
110  t0 = particle%ttrack
111 
112  ! Find exit solution in each direction
113  call find_exits(particle, subcell, exit_solutions, x0, y0, z0)
114  exit_x = exit_solutions(1)
115  exit_y = exit_solutions(2)
116  exit_z = exit_solutions(3)
117 
118  ! Set solution, face, & travel time
119  exit_soln = pick_exit(exit_solutions)
120  if (exit_soln == 0) then
121  exit_face = 0
122  dtexit = 1.0d+30
123  else
124  exit_face = exit_solutions(exit_soln)%iboundary
125  dtexit = exit_solutions(exit_soln)%dt
126  end if
127  texit = particle%ttrack + dtexit
128 
129  ! Terminate if no valid exit solution was found.
130  ! MP7 is more nuanced in determining what to do
131  ! here. It considers whether this stress period
132  ! is steady state or transient, and whether the
133  ! flow is stationary, in determining whether to
134  ! terminate or allow the particle to survive to
135  ! the next time step. It does not compute paths
136  ! within the subcell even for particles it lets
137  ! remain active under this circumstance, though.
138  ! While we may consider that someday, we simply
139  ! terminate and sidestep the complexity for now.
140  if (all([exit_solutions%status] >= no_exit_stationary)) then
141  call this%terminate(particle, status=term_no_exits_sub)
142  return
143  end if
144 
145  ! Select user tracking times to solve. If this is the first time step
146  ! of the simulation, include all times before it begins; if it is the
147  ! last time step include all times after it ends only if the 'extend'
148  ! option is on, otherwise times in this period and time step only.
149  call this%tracktimes%advance()
150  if (this%tracktimes%any()) then
151  do i = this%tracktimes%selection(1), this%tracktimes%selection(2)
152  t = this%tracktimes%times(i)
153  if (t < particle%ttrack) cycle
154  if (t > texit .or. t > tmax) exit
155  dt = t - t0
156  x = new_x(exit_x%v, exit_x%dvdx, subcell%vx1, subcell%vx2, &
157  dt, x0, subcell%dx, exit_x%status == 1)
158  y = new_x(exit_y%v, exit_y%dvdx, subcell%vy1, subcell%vy2, &
159  dt, y0, subcell%dy, exit_y%status == 1)
160  z = new_x(exit_z%v, exit_z%dvdx, subcell%vz1, subcell%vz2, &
161  dt, z0, subcell%dz, exit_z%status == 1)
162  particle%x = x * subcell%dx
163  particle%y = y * subcell%dy
164  particle%z = z * subcell%dz
165  particle%ttrack = t
166  particle%istatus = active
167  call this%usertime(particle)
168  end do
169  end if
170 
171  ! Computed exit time greater than the maximum time? Set the
172  ! tracking time to tmax and calculate the particle location.
173  if (texit .gt. tmax) then
174  t = tmax
175  dt = t - t0
176  x = new_x(exit_x%v, exit_x%dvdx, subcell%vx1, subcell%vx2, &
177  dt, x0, subcell%dx, exit_x%status == 1)
178  y = new_x(exit_y%v, exit_y%dvdx, subcell%vy1, subcell%vy2, &
179  dt, y0, subcell%dy, exit_y%status == 1)
180  z = new_x(exit_z%v, exit_z%dvdx, subcell%vz1, subcell%vz2, &
181  dt, z0, subcell%dz, exit_z%status == 1)
182  exit_face = 0
183  particle%istatus = active
184  particle%advancing = .false.
185  particle%x = x * subcell%dx
186  particle%y = y * subcell%dy
187  particle%z = z * subcell%dz
188  particle%ttrack = t
189  particle%iboundary(level_subfeature) = exit_face
190  call this%timestep(particle)
191  return
192  end if
193 
194  ! If we get to here, the particle is exiting the subcell.
195  ! Its exit time is less than or equal to the maximum time.
196  ! Set tracking time to the exit time and set exit location.
197  t = texit
198  dt = dtexit
199  if ((exit_face .eq. 1) .or. (exit_face .eq. 2)) then
200  x = dzero
201  y = new_x(exit_y%v, exit_y%dvdx, subcell%vy1, subcell%vy2, &
202  dt, y0, subcell%dy, exit_y%status == 1)
203  z = new_x(exit_z%v, exit_z%dvdx, subcell%vz1, subcell%vz2, &
204  dt, z0, subcell%dz, exit_z%status == 1)
205  if (exit_face .eq. 2) x = done
206  else if ((exit_face .eq. 3) .or. (exit_face .eq. 4)) then
207  x = new_x(exit_x%v, exit_x%dvdx, subcell%vx1, subcell%vx2, dt, &
208  x0, subcell%dx, exit_x%status == 1)
209  y = dzero
210  z = new_x(exit_z%v, exit_z%dvdx, subcell%vz1, subcell%vz2, dt, &
211  z0, subcell%dz, exit_z%status == 1)
212  if (exit_face .eq. 4) y = done
213  else if ((exit_face .eq. 5) .or. (exit_face .eq. 6)) then
214  x = new_x(exit_x%v, exit_x%dvdx, subcell%vx1, subcell%vx2, &
215  dt, x0, subcell%dx, exit_x%status == 1)
216  y = new_x(exit_y%v, exit_y%dvdx, subcell%vy1, subcell%vy2, &
217  dt, y0, subcell%dy, exit_y%status == 1)
218  z = dzero
219  if (exit_face .eq. 6) z = done
220  else
221  print *, "programmer error, invalid exit face", exit_face
222  call pstop(1)
223  end if
224  particle%x = x * subcell%dx
225  particle%y = y * subcell%dy
226  particle%z = z * subcell%dz
227  particle%ttrack = t
228  particle%iboundary(level_subfeature) = exit_face
229  call this%subcellexit(particle)
230 
231  end subroutine track_subcell
232 
233  !> @brief Pick the exit solution with the shortest travel time
234  function pick_exit(exit_solutions) result(exit_soln)
235  type(linearexitsolutiontype), intent(in) :: exit_solutions(3)
236  integer(I4B) :: exit_soln
237  ! local
238  real(dp) :: dtmin
239 
240  exit_soln = 0
241  dtmin = 1.0d+30
242 
243  if (exit_solutions(1)%status < 2) then
244  exit_soln = 1 ! x
245  dtmin = exit_solutions(1)%dt
246  end if
247  if (exit_solutions(2)%status < 2 .and. &
248  exit_solutions(2)%dt < dtmin) then
249  exit_soln = 2 ! y
250  dtmin = exit_solutions(2)%dt
251  end if
252  if (exit_solutions(3)%status < 2 .and. &
253  exit_solutions(3)%dt < dtmin) then
254  exit_soln = 3 ! z
255  end if
256 
257  end function pick_exit
258 
259  !> @brief Compute candidate exit solutions
260  subroutine find_exits(particle, domain, exit_solutions, x0, y0, z0)
261  type(particletype), pointer, intent(inout) :: particle
262  class(domaintype), intent(in) :: domain
263  type(linearexitsolutiontype), intent(out) :: exit_solutions(3)
264  real(DP), intent(out) :: x0, y0, z0
265 
266  select type (domain)
267  type is (subcellrecttype)
268  ! Initial particle location in scaled subcell coordinates
269  x0 = particle%x / domain%dx
270  y0 = particle%y / domain%dy
271  z0 = particle%z / domain%dz
272 
273  ! Calculate exit solutions for each coordinate direction
274  exit_solutions = [ &
275  find_exit(domain%vx1, domain%vx2, domain%dx, x0), &
276  find_exit(domain%vy1, domain%vy2, domain%dy, y0), &
277  find_exit(domain%vz1, domain%vz2, domain%dz, z0) &
278  ]
279 
280  ! Set exit faces
281  if (exit_solutions(1)%v < dzero) then
282  exit_solutions(1)%iboundary = 1
283  else if (exit_solutions(1)%v > dzero) then
284  exit_solutions(1)%iboundary = 2
285  end if
286  if (exit_solutions(2)%v < dzero) then
287  exit_solutions(2)%iboundary = 3
288  else if (exit_solutions(2)%v > dzero) then
289  exit_solutions(2)%iboundary = 4
290  end if
291  if (exit_solutions(3)%v < dzero) then
292  exit_solutions(3)%iboundary = 5
293  else if (exit_solutions(3)%v > dzero) then
294  exit_solutions(3)%iboundary = 6
295  end if
296  end select
297  end subroutine find_exits
298 
299  !> @brief Find an exit solution for one dimension
300  function find_exit(v1, v2, dx, xL) result(solution)
301  ! dummy
302  real(dp), intent(in) :: v1
303  real(dp), intent(in) :: v2
304  real(dp), intent(in) :: dx
305  real(dp), intent(in) :: xl
306  type(linearexitsolutiontype) :: solution
307 
308  solution = linearexitsolutiontype()
309  solution%status = calculate_dt(v1, v2, dx, xl, &
310  solution%v, solution%dvdx, solution%dt)
311  end function find_exit
312 
313  !> @brief Calculate particle travel time to exit and exit status.
314  !!
315  !! This subroutine consists partly of code written by and/or adapted from
316  !! David W. Pollock of the USGS for MODPATH 7. The authors of the present
317  !! code are responsible for its appropriate application in this context
318  !! and for any modifications or errors.
319  !<
320  function calculate_dt(v1, v2, dx, xL, v, dvdx, dt) result(status)
321  ! dummy
322  real(dp) :: v1
323  real(dp) :: v2
324  real(dp) :: dx
325  real(dp) :: xl
326  real(dp) :: v
327  real(dp) :: dvdx
328  real(dp) :: dt
329  ! result
330  integer(I4B) :: status
331  ! local
332  real(dp) :: v2a
333  real(dp) :: v1a
334  real(dp) :: dv
335  real(dp) :: dva
336  real(dp) :: vv
337  real(dp) :: vvv
338  real(dp) :: zro
339  real(dp) :: zrom
340  real(dp) :: x
341  real(dp) :: tol
342  real(dp) :: vr1
343  real(dp) :: vr2
344  real(dp) :: vr
345  real(dp) :: v1v2
346  logical(LGP) :: nooutflow
347 
348  ! Initialize variables.
349  status = -1
350  dt = 1.0d+20
351  v2a = v2
352  if (v2a .lt. dzero) v2a = -v2a
353  v1a = v1
354  if (v1a .lt. dzero) v1a = -v1a
355  dv = v2 - v1
356  dva = dv
357  if (dva .lt. dzero) dva = -dva
358 
359  ! Check for a uniform zero velocity in this direction.
360  ! If so, set status = 2 and return (dt = 1.0d+20).
361  tol = 1.0d-15
362  if ((v2a .lt. tol) .and. (v1a .lt. tol)) then
363  v = dzero
364  dvdx = dzero
365  status = no_exit_stationary
366  return
367  end if
368 
369  ! Check for uniform non-zero velocity in this direction.
370  ! If so, set compute dt using the constant velocity,
371  ! set status = 1 and return.
372  vv = v1a
373  if (v2a .gt. vv) vv = v2a
374  vvv = dva / vv
375  if (vvv .lt. 1.0d-4) then
376  zro = tol
377  zrom = -zro
378  v = v1
379  x = xl * dx
380  if (v1 .gt. zro) dt = (dx - x) / v1
381  if (v1 .lt. zrom) dt = -x / v1
382  dvdx = dzero
383  status = ok_exit_constant
384  return
385  end if
386 
387  ! Velocity has a linear variation.
388  ! Compute velocity corresponding to particle position.
389  dvdx = dv / dx
390  v = (done - xl) * v1 + xl * v2
391 
392  ! If flow is into the cell from both sides there is no outflow.
393  ! In that case, set status = 3 and return.
394  nooutflow = .true.
395  if (v1 .lt. dzero) nooutflow = .false.
396  if (v2 .gt. dzero) nooutflow = .false.
397  if (nooutflow) then
398  status = no_exit_no_outflow
399  return
400  end if
401 
402  ! If there is a divide in the cell for this flow direction, check to
403  ! see if the particle is located exactly on the divide. If it is, move
404  ! it very slightly to get it off the divide. This avoids possible
405  ! numerical problems related to stagnation points.
406  if ((v1 .le. dzero) .and. (v2 .ge. dzero)) then
407  if (abs(v) .le. dzero) then
408  v = 1.0d-20
409  if (v2 .le. dzero) v = -v
410  end if
411  end if
412 
413  ! If there is a flow divide, this check finds out what side of the
414  ! divide the particle is on and sets the value of vr appropriately
415  ! to reflect that location.
416  vr1 = v1 / v
417  vr2 = v2 / v
418  vr = vr1
419  if (vr .le. dzero) then
420  vr = vr2
421  end if
422 
423  ! If the product v1*v2 > 0, the velocity is in the same direction
424  ! throughout the cell (i.e. no flow divide). If so, set the value
425  ! of vr to reflect the appropriate direction.
426  v1v2 = v1 * v2
427  if (v1v2 .gt. dzero) then
428  if (v .gt. dzero) vr = vr2
429  if (v .lt. dzero) vr = vr1
430  end if
431 
432  ! Check if vr is (very close to) zero.
433  ! If so, set status = 2 and return (dt = 1.0d+20).
434  if (dabs(vr) .lt. 1.0d-10) then
435  v = dzero
436  dvdx = dzero
437  status = no_exit_stationary
438  return
439  end if
440 
441  ! Compute travel time to exit face. Return with status = 0.
442  dt = log(vr) / dvdx
443  status = ok_exit
444 
445  end function calculate_dt
446 
447  !> @brief Update a cell-local coordinate based on a time increment.
448  !!
449  !! This subroutine consists partly or entirely of code written by
450  !! David W. Pollock of the USGS for MODPATH 7. The authors of the present
451  !! code are responsible for its appropriate application in this context
452  !! and for any modifications or errors.
453  !<
454  pure function new_x(v, dvdx, v1, v2, dt, x, dx, velocity_profile) result(newx)
455  ! dummy
456  real(dp), intent(in) :: v
457  real(dp), intent(in) :: dvdx
458  real(dp), intent(in) :: v1
459  real(dp), intent(in) :: v2
460  real(dp), intent(in) :: dt
461  real(dp), intent(in) :: x
462  real(dp), intent(in) :: dx
463  logical(LGP), intent(in), optional :: velocity_profile
464  ! result
465  real(dp) :: newx
466  logical(LGP) :: lprofile
467 
468  ! process optional arguments
469  if (present(velocity_profile)) then
470  lprofile = velocity_profile
471  else
472  lprofile = .false.
473  end if
474 
475  ! recompute coordinate
476  newx = x
477  if (lprofile) then
478  newx = newx + (v1 * dt / dx)
479  else if (v .ne. dzero) then
480  newx = newx + (v * (exp(dvdx * dt) - done) / dvdx / dx)
481  end if
482 
483  ! clamp to [0, 1]
484  if (newx .lt. dzero) newx = dzero
485  if (newx .gt. done) newx = done
486 
487  end function new_x
488 
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