Skip to content

Commit 0ead0bc

Browse files
authored
fix(prt): prevent top exits from partially saturated cells (#2478)
Cyclic particle pathlines were possible in Newton models with DRY_TRACKING_METHOD DROP (the default) due to numerically insignificant upward flows in partially saturated cells. This occurred because PRT did not distinguish the water table from the cell top, so a particle at the water table in a partially saturated cell could obtain an exit solution through the cell's top face, jump to the dry cell above, then drop back down, and so on. Prevent particles from exiting through the top face of partially saturated cells. If a particle obtains an exit solution upwards through the water table, its behavior now depends on whether the cell top is an assigned boundary face, and the DRY_TRACKING_METHOD setting. If the cell top is an assigned boundary, or if DRY_TRACKING_METHOD is STOP, the particle will terminate. Otherwise it will stay in that location. (Future work may expand on this behavior; this is a minimal bug fix.) The issue is reproducible on the Keating example, so I added no new tests here. I added a snapshot comparison to Keating to check the new behavior. This fix allows removing the old ad hoc trap for cycles between vertical faces. Also some other misc cleanup.
1 parent 2b793e4 commit 0ead0bc

File tree

10 files changed

+70
-79
lines changed

10 files changed

+70
-79
lines changed

doc/ReleaseNotes/develop.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,8 @@ description = "Fixes a memory exception that has occurred when running parallel
9797
section = "fixes"
9898
subsection = "solution"
9999
description = "The PRT Model has previously been aware of boundary cell flows but not boundary faces. When an internal boundary face is assigned (an IFLOWFACE adjacent to another active cell), this can lead to cycles (infinite loops) in the particle's pathline, as the two adjacent cells fail to agree on the flow through their shared face. The PRT Model will now terminate particles at assigned boundary faces, whether they are internal or external, if the net boundary flow through the face is out of the cell."
100+
101+
[[items]]
102+
section = "fixes"
103+
subsection = "solution"
104+
description = "Cyclic particle pathlines were possible in Newton models with DRY\\_TRACKING\\_METHOD DROP (the default) in the presence of numerically insignificant upward flows in partially saturated cells. This occurred because PRT did not distinguish the water table from the cell top, so a particle at the water table in a partially saturated cell could obtain an exit solution through the cell's top face, jump to the dry cell above, then drop back down, and so on. Particles are now prevented from exiting through the top face of partially saturated cells. If a particle reaches the water table, its behavior now depends on a combination of DRY\\_TRACKING\\_METHOD and whether the cell top is an assigned boundary face. If the cell top is an assigned boundary face, or if DRY\\_TRACKING\\_METHOD is STOP, the particle will terminate. Otherwise, it will stay there until tracking resumes the next time step."

src/Model/ParticleTracking/prt.f90

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -968,10 +968,6 @@ subroutine prt_solve(this)
968968
end if
969969
! Apply the tracking method until the maximum time.
970970
call this%method%apply(particle, tmax)
971-
! Reset cell/zone one-backs, used for cycle detection.
972-
! TODO can remove when we have better cycle detection
973-
particle%icp = 0
974-
particle%izp = 0
975971
! If the particle timed out, terminate it.
976972
! "Timed out" means it's still active but
977973
! - it reached its stop time, or

src/Solution/ParticleTracker/Method/ExitSolution.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module ExitSolutionModule
88
!> @brief Exit status codes
99
enum, bind(C)
1010
enumerator :: OK_EXIT = 0 !< exit found using velocity interpolation
11-
! below only used for linear interp
11+
! below only used for linear solution
1212
enumerator :: OK_EXIT_CONSTANT = 1 !< exit found, constant velocity
1313
enumerator :: NO_EXIT_STATIONARY = 2 !< no exit, zero velocity
1414
enumerator :: NO_EXIT_NO_OUTFLOW = 3 !< no exit, no outflow

src/Solution/ParticleTracker/Method/MethodCell.f90

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,41 @@ module MethodCellModule
2929
subroutine try_pass(this, particle, nextlevel, advancing)
3030
class(MethodCellType), intent(inout) :: this
3131
type(ParticleType), pointer, intent(inout) :: particle
32-
integer(I4B) :: nextlevel
33-
logical(LGP) :: advancing
34-
35-
if (particle%advancing) then
36-
! if still advancing, pass to the next subdomain.
37-
! if that puts us on a boundary, then we're done.
38-
! raise a cell exit event.
39-
call this%pass(particle)
40-
if (particle%iboundary(nextlevel - 1) .ne. 0) then
41-
advancing = .false.
42-
call this%cellexit(particle)
43-
end if
44-
else
45-
! otherwise we're already done so
46-
! reset the domain boundary value.
32+
integer(I4B) :: nextlevel, ic, iface
33+
logical(LGP) :: advancing, on_face, on_top_face, partly_sat
34+
35+
if (.not. particle%advancing) then
4736
advancing = .false.
4837
particle%iboundary = 0
38+
return
39+
end if
40+
41+
call this%pass(particle)
42+
43+
ic = particle%itrdomain(LEVEL_FEATURE)
44+
iface = particle%iboundary(LEVEL_FEATURE) - 1 ! cell is closed
45+
on_face = iface >= 0
46+
on_top_face = this%fmi%max_faces == iface
47+
partly_sat = this%fmi%gwfsat(this%cell%defn%icell) < DONE
48+
49+
! if at top and the cell is partially saturated,
50+
! we're at the water table, not the cell top, so
51+
! no exit event. if cell top is a boundary face,
52+
! terminate. if the dry tracking method is stop,
53+
! terminate. otherwise, leave the particle where
54+
! it is- keep tracking it on the next time step.
55+
if (on_top_face .and. partly_sat) then
56+
advancing = .false.
57+
particle%advancing = .false.
58+
if (this%fmi%is_net_out_boundary_face(ic, iface) .or. &
59+
particle%idrymeth == 1) & ! dry_tracking_method stop
60+
call this%terminate(particle, status=TERM_BOUNDARY)
61+
return
62+
end if
63+
64+
if (on_face) then
65+
advancing = .false.
66+
call this%cellexit(particle)
4967
end if
5068
end subroutine try_pass
5169

src/Solution/ParticleTracker/Method/MethodCellPollock.f90

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ subroutine load_mcp(this, particle, next_level, submethod)
6464
call this%load_subcell(particle, subcell)
6565
end select
6666
call method_subcell_plck%init( &
67+
fmi=this%fmi, &
6768
cell=this%cell, &
6869
subcell=this%subcell, &
6970
events=this%events, &
@@ -162,7 +163,8 @@ subroutine load_subcell(this, particle, subcell) !
162163

163164
select type (cell => this%cell)
164165
type is (CellRectType)
165-
! Set subcell number to 1
166+
! Set cell/subcell numbers
167+
subcell%icell = cell%defn%icell
166168
subcell%isubcell = 1
167169

168170
! Subcell calculations will be done in local subcell coordinates

src/Solution/ParticleTracker/Method/MethodDis.f90

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -204,18 +204,6 @@ subroutine load_particle(this, cell, particle)
204204
icu = dis%get_nodeuser(ic)
205205
call get_ijk(icu, dis%nrow, dis%ncol, dis%nlay, irow, icol, ilay)
206206

207-
! TODO remove this cycle trap as soon as both cycle conditions are fixed!
208-
if (ic == particle%icp .and. inface == 7 .and. ilay < particle%ilay) then
209-
particle%itrdomain(LEVEL_FEATURE) = particle%icp
210-
particle%izone = particle%izp
211-
call this%terminate(particle, &
212-
status=TERM_BOUNDARY)
213-
return
214-
else
215-
particle%icp = particle%itrdomain(LEVEL_FEATURE)
216-
particle%izp = particle%izone
217-
end if
218-
219207
! update node numbers and layer
220208
particle%itrdomain(LEVEL_FEATURE) = ic
221209
particle%icu = icu
@@ -288,7 +276,7 @@ subroutine pass_dis(this, particle)
288276
! local
289277
type(CellRectType), pointer :: cell
290278
integer(I4B) :: iface
291-
logical(LGP) :: no_neighbors, at_boundary
279+
logical(LGP) :: at_boundary, no_neighbors
292280

293281
select type (c => this%cell)
294282
type is (CellRectType)
@@ -304,7 +292,6 @@ subroutine pass_dis(this, particle)
304292
end if
305293

306294
call this%load_particle(cell, particle)
307-
if (.not. particle%advancing) return ! todo: remove after cycles fixed
308295
call this%update_flowja(cell, particle)
309296
end select
310297
end subroutine pass_dis

src/Solution/ParticleTracker/Method/MethodDisv.f90

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,6 @@ subroutine load_particle(this, cell, particle)
166166
icu = dis%get_nodeuser(ic)
167167
call get_jk(icu, dis%ncpl, dis%nlay, icpl, ilay)
168168

169-
! TODO remove this cycle trap as soon as both cycle conditions are fixed!
170-
if (ic == particle%icp .and. inface == 7 .and. ilay < particle%ilay) then
171-
particle%itrdomain(LEVEL_FEATURE) = particle%icp
172-
particle%izone = particle%izp
173-
call this%terminate(particle, status=TERM_BOUNDARY)
174-
return
175-
else
176-
particle%icp = particle%itrdomain(LEVEL_FEATURE)
177-
particle%izp = particle%izone
178-
end if
179-
180169
particle%itrdomain(LEVEL_FEATURE) = ic
181170
particle%icu = icu
182171
particle%ilay = ilay
@@ -225,7 +214,7 @@ subroutine pass_disv(this, particle)
225214
! local
226215
type(CellPolyType), pointer :: cell
227216
integer(I4B) :: iface
228-
logical(LGP) :: no_neighbors, at_boundary
217+
logical(LGP) :: at_boundary, no_neighbors
229218

230219
select type (c => this%cell)
231220
type is (CellPolyType)
@@ -241,7 +230,6 @@ subroutine pass_disv(this, particle)
241230
end if
242231

243232
call this%load_particle(cell, particle)
244-
if (.not. particle%advancing) return ! todo: remove after cycles fixed
245233
call this%update_flowja(cell, particle)
246234
end select
247235
end subroutine pass_disv

src/Solution/ParticleTracker/Method/MethodSubcellPollock.f90

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,15 @@ subroutine track_subcell(this, subcell, particle, tmax)
115115
y0 = particle%y / subcell%dy
116116
z0 = particle%z / subcell%dz
117117

118-
! Find exit solutions for each coordinate direction
118+
! Find exit solution in each direction
119119
call this%find_exits(particle, subcell)
120120

121121
exit_x = this%exit_solutions(1)
122122
exit_y = this%exit_solutions(2)
123123
exit_z = this%exit_solutions(3)
124124

125125
! Subcell has no exit face, terminate the particle
126-
! todo: after initial release, consider ramifications
126+
! TODO: consider ramifications
127127
if (all([this%exit_solutions%status] == NO_EXIT_NO_OUTFLOW)) then
128128
call this%terminate(particle, status=TERM_NO_EXITS_SUB)
129129
return
@@ -260,19 +260,19 @@ function pick_exit(this, particle) result(exit_soln)
260260

261261
exit_soln = 0
262262
dtmin = 1.0d+30
263+
263264
if (this%exit_solutions(1)%status < 2) then
264-
exit_soln = 1
265+
exit_soln = 1 ! x
265266
dtmin = this%exit_solutions(1)%dt
266267
end if
267268
if (this%exit_solutions(2)%status < 2 .and. &
268269
this%exit_solutions(2)%dt < dtmin) then
269-
exit_soln = 2
270+
exit_soln = 2 ! y
270271
dtmin = this%exit_solutions(2)%dt
271272
end if
272273
if (this%exit_solutions(3)%status < 2 .and. &
273274
this%exit_solutions(3)%dt < dtmin) then
274-
exit_soln = 3
275-
dtmin = this%exit_solutions(3)%dt
275+
exit_soln = 3 ! z
276276
end if
277277

278278
end function pick_exit

src/Solution/ParticleTracker/Method/MethodSubcellTernary.f90

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ subroutine track_subcell(this, subcell, particle, tmax)
119119
exit_lateral = this%exit_solutions(2)
120120

121121
! If the subcell has no exit face, terminate the particle.
122-
! todo: after initial release, consider ramifications
122+
! TODO: consider ramifications
123123
if (exit_z%itopbotexit == 0 .and. &
124124
exit_lateral%itrifaceexit == 0) then
125125
call this%terminate(particle, status=TERM_NO_EXITS_SUB)
@@ -214,23 +214,14 @@ function pick_exit(this, particle) result(exit_soln)
214214
integer(I4B) :: exit_soln
215215

216216
if (this%exit_solutions(1)%itopbotexit == 0) then
217-
! Exits through triangle face first
218-
exit_soln = 2
219-
this%exit_solutions(2)%iboundary = this%exit_solutions(2)%itrifaceexit
217+
exit_soln = 2 ! lateral
220218
else if (this%exit_solutions(2)%itrifaceexit == 0 .or. &
221219
this%exit_solutions(1)%dt < this%exit_solutions(2)%dt) then
222-
! Exits through top/bottom first
223-
exit_soln = 1
224-
if (this%exit_solutions(1)%itopbotexit == -1) then
225-
this%exit_solutions(1)%iboundary = 4
226-
else
227-
this%exit_solutions(1)%iboundary = 5
228-
end if
220+
exit_soln = 1 ! top/bottom
229221
else
230-
! Exits through triangle face first
231-
exit_soln = 2
232-
this%exit_solutions(2)%iboundary = this%exit_solutions(2)%itrifaceexit
222+
exit_soln = 2 ! lateral
233223
end if
224+
234225
end function pick_exit
235226

236227
!> @brief Calculate exit solutions for each coordinate direction
@@ -264,7 +255,7 @@ subroutine find_exits(this, particle, domain)
264255
ntmax = 10000
265256
tol = particle%extol
266257

267-
! Set solution method
258+
! Set lateral solution method
268259
if (particle%iexmeth == 0) then
269260
isolv = 1 ! default to Brent's
270261
else
@@ -310,7 +301,7 @@ subroutine find_exits(this, particle, domain)
310301
this%exit_solutions(1) = find_vertical_exit(subcell%vzbot, subcell%vztop, &
311302
subcell%dz, zirel)
312303

313-
! Calculate a lateral exit solution semi-analytically.
304+
! Calculate a semi-analytical lateral exit solution
314305
itrifaceenter = particle%iboundary(LEVEL_SUBFEATURE) - 1
315306
if (itrifaceenter == -1) itrifaceenter = 999
316307
this%exit_solutions(2) = find_lateral_exit(isolv, tol, &
@@ -324,6 +315,19 @@ subroutine find_exits(this, particle, domain)
324315
this%exit_solutions(2)%sxx = sxx
325316
this%exit_solutions(2)%sxy = sxy
326317
this%exit_solutions(2)%syy = syy
318+
319+
! Set vertical solution exit face
320+
if (this%exit_solutions(1)%itopbotexit /= 0) then
321+
if (this%exit_solutions(1)%itopbotexit == -1) then
322+
this%exit_solutions(1)%iboundary = 4
323+
else
324+
this%exit_solutions(1)%iboundary = 5
325+
end if
326+
end if
327+
328+
! Set lateral solution exit face
329+
if (this%exit_solutions(2)%itrifaceexit /= 0) &
330+
this%exit_solutions(2)%iboundary = this%exit_solutions(2)%itrifaceexit
327331
end select
328332
end subroutine find_exits
329333

src/Solution/ParticleTracker/Particle/Particle.f90

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,9 @@ module ParticleModule
7373
! state
7474
integer(I4B), public :: itrdomain(MAX_LEVEL) !< tracking domain indices
7575
integer(I4B), public :: iboundary(MAX_LEVEL) !< tracking domain boundary indices
76-
integer(I4B), public :: icp !< previous cell number (reduced)
7776
integer(I4B), public :: icu !< user cell number
7877
integer(I4B), public :: ilay !< grid layer
7978
integer(I4B), public :: izone !< current zone number
80-
integer(I4B), public :: izp !< previous zone number
8179
integer(I4B), public :: istatus !< tracking status
8280
real(DP), public :: x !< x coordinate
8381
real(DP), public :: y !< y coordinate
@@ -125,7 +123,6 @@ module ParticleModule
125123
integer(I4B), dimension(:), pointer, public, contiguous :: icu !< cell number (user)
126124
integer(I4B), dimension(:), pointer, public, contiguous :: ilay !< layer
127125
integer(I4B), dimension(:), pointer, public, contiguous :: izone !< current zone number
128-
integer(I4B), dimension(:), pointer, public, contiguous :: izp !< previous zone number
129126
integer(I4B), dimension(:), pointer, public, contiguous :: istatus !< particle status
130127
real(DP), dimension(:), pointer, public, contiguous :: x !< model x coord of particle
131128
real(DP), dimension(:), pointer, public, contiguous :: y !< model y coord of particle
@@ -164,7 +161,6 @@ subroutine create_particle_store(store, np, mempath)
164161
call mem_allocate(store%icu, np, 'PLICU', mempath)
165162
call mem_allocate(store%ilay, np, 'PLILAY', mempath)
166163
call mem_allocate(store%izone, np, 'PLIZONE', mempath)
167-
call mem_allocate(store%izp, np, 'PLIZP', mempath)
168164
call mem_allocate(store%istatus, np, 'PLISTATUS', mempath)
169165
call mem_allocate(store%x, np, 'PLX', mempath)
170166
call mem_allocate(store%y, np, 'PLY', mempath)
@@ -196,7 +192,6 @@ subroutine destroy(this, mempath)
196192
call mem_deallocate(this%icu, 'PLICU', mempath)
197193
call mem_deallocate(this%ilay, 'PLILAY', mempath)
198194
call mem_deallocate(this%izone, 'PLIZONE', mempath)
199-
call mem_deallocate(this%izp, 'PLIZP', mempath)
200195
call mem_deallocate(this%istatus, 'PLISTATUS', mempath)
201196
call mem_deallocate(this%x, 'PLX', mempath)
202197
call mem_deallocate(this%y, 'PLY', mempath)
@@ -237,7 +232,6 @@ subroutine resize(this, np, mempath)
237232
call mem_reallocate(this%icu, np, 'PLICU', mempath)
238233
call mem_reallocate(this%ilay, np, 'PLILAY', mempath)
239234
call mem_reallocate(this%izone, np, 'PLIZONE', mempath)
240-
call mem_reallocate(this%izp, np, 'PLIZP', mempath)
241235
call mem_reallocate(this%istatus, np, 'PLISTATUS', mempath)
242236
call mem_reallocate(this%x, np, 'PLX', mempath)
243237
call mem_reallocate(this%y, np, 'PLY', mempath)
@@ -279,11 +273,9 @@ subroutine get(this, particle, imdl, iprp, ip)
279273
particle%istopweaksink = this%istopweaksink(ip)
280274
particle%istopzone = this%istopzone(ip)
281275
particle%idrymeth = this%idrymeth(ip)
282-
particle%icp = 0
283276
particle%icu = this%icu(ip)
284277
particle%ilay = this%ilay(ip)
285278
particle%izone = this%izone(ip)
286-
particle%izp = this%izp(ip)
287279
particle%istatus = this%istatus(ip)
288280
particle%x = this%x(ip)
289281
particle%y = this%y(ip)
@@ -320,7 +312,6 @@ subroutine put(this, particle, ip)
320312
this%icu(ip) = particle%icu
321313
this%ilay(ip) = particle%ilay
322314
this%izone(ip) = particle%izone
323-
this%izp(ip) = particle%izp
324315
this%istatus(ip) = particle%istatus
325316
this%x(ip) = particle%x
326317
this%y(ip) = particle%y

0 commit comments

Comments
 (0)