MODFLOW 6  version 6.9.0.dev0
USGS Modular Hydrologic Model
TimeSelect.f90
Go to the documentation of this file.
1 !> @brief Specify times for some event to occur.
3 
4  use kindmodule, only: dp, i4b, lgp
5  use constantsmodule, only: dzero, done
7  use errorutilmodule, only: pstop
8  use sortmodule, only: qsort
9  use mathutilmodule, only: is_close
10 
11  implicit none
12  public :: timeselecttype
13 
14  !> @brief Represents a series of instants at which some event should occur.
15  !!
16  !! Maintains an array of configured times which can be sliced to match e.g.
17  !! the current period & time step. Slicing can be performed manually, with
18  !! the select() routine, or automatically, with the advance() routine, for
19  !! a convenient view onto the applicable subset of the complete time array.
20  !!
21  !! Time selection uses the interval convention (t0, t1] (exclusive lower
22  !! bound, inclusive upper bound). This ensures times at exact time step
23  !! boundaries are captured by the earlier time step without duplication.
24  !!
25  !! Array storage can be expanded manually. Note: array expansion must take
26  !! place before selection; when expand() is called the selection is wiped.
27  !! Alternatively, the extend() routine will automatically expand the array
28  !! and sort it.
29  !!
30  !! Most use cases likely assume a strictly increasing time selection; this
31  !! can be checked with increasing(). Note that the sort() routine does not
32  !! check for duplicates, and should usually be followed by an increasing()
33  !! check before the time selection is used.
34  !<
36  real(dp), allocatable :: times(:)
37  integer(I4B) :: selection(2)
38  contains
39  procedure :: deallocate
40  procedure :: expand
41  procedure :: init
42  procedure :: increasing
43  procedure :: log
44  procedure :: select
45  procedure :: advance
46  procedure :: any
47  procedure :: count
48  procedure :: sort
49  procedure :: extend
50  procedure :: contains_close
51  end type timeselecttype
52 
53 contains
54 
55  !> @brief Deallocate the time selection object.
56  subroutine deallocate (this)
57  class(timeselecttype) :: this
58  deallocate (this%times)
59  end subroutine deallocate
60 
61  !> @brief Expand capacity by the given amount. Resets the current slice.
62  subroutine expand(this, increment)
63  class(timeselecttype) :: this
64  integer(I4B), optional, intent(in) :: increment
65 
66  call expandarray(this%times, increment=increment)
67  this%selection = (/1, size(this%times)/)
68  end subroutine expand
69 
70  !> @brief Initialize or clear the time selection object.
71  subroutine init(this)
72  class(timeselecttype) :: this
73 
74  if (allocated(this%times)) deallocate (this%times)
75  allocate (this%times(0))
76  this%selection = (/0, 0/)
77  end subroutine
78 
79  !> @brief Determine if times strictly increase.
80  !!
81  !! Returns true if the times array strictly increases,
82  !! as well as if the times array is empty, or not yet
83  !! allocated. Note that this function operates on the
84  !! entire times array, not the current selection. Note
85  !! also that this function conducts exact comparisons;
86  !! deduplication with tolerance must be done manually.
87  !<
88  function increasing(this) result(inc)
89  class(timeselecttype) :: this
90  logical(LGP) :: inc
91  integer(I4B) :: i
92  real(dp) :: l, t
93 
94  inc = .true.
95  if (.not. allocated(this%times)) return
96  do i = 1, size(this%times)
97  t = this%times(i)
98  if (i /= 1) then
99  if (l >= t) then
100  inc = .false.
101  return
102  end if
103  end if
104  l = t
105  end do
106  end function increasing
107 
108  !> @brief Show the current time selection, if any.
109  subroutine log(this, iout, verb)
110  ! dummy
111  class(timeselecttype) :: this !< this instance
112  integer(I4B), intent(in) :: iout !< output unit
113  character(len=*), intent(in) :: verb !< selection name
114  ! formats
115  character(len=*), parameter :: fmt = &
116  &"(6x,'THE FOLLOWING TIMES WILL BE ',A,': ',50(G0,' '))"
117 
118  if (this%any()) then
119  write (iout, fmt) verb, this%times(this%selection(1):this%selection(2))
120  else
121  write (iout, "(a,1x,a)") 'NO TIMES WILL BE', verb
122  end if
123  end subroutine log
124 
125  !> @brief Select times in the interval (t0, t1] (exclusive lower, inclusive upper).
126  !!
127  !! Finds and stores the index of the first time strictly after the start time,
128  !! and of the last time at or before the end time. This interval convention
129  !! ensures that times at exact time step boundaries are captured by the later
130  !! (earlier in simulation time) step without duplication.
131  !!
132  !! Allows filtering the times for e.g. a particular stress period and time step.
133  !! Array indices are assumed to start at 1. If no times are found to fall within
134  !! the selection (i.e. the interval falls entirely between two consecutive times
135  !! or beyond the time range), indices are set to [-1, -1].
136  !!
137  !! The given start and end times are first checked against currently stored
138  !! indices to avoid recalculating them if possible, allowing multiple consuming
139  !! components (e.g., subdomain particle tracking solutions) to share the object
140  !! efficiently, provided all proceed through stress periods and time steps in
141  !! lockstep, i.e. they all solve any given period/step before any will proceed
142  !! to the next.
143  !<
144  subroutine select(this, t0, t1, changed)
145  ! dummy
146  class(timeselecttype) :: this
147  real(DP), intent(in) :: t0, t1
148  logical(LGP), intent(inout), optional :: changed
149  ! local
150  integer(I4B) :: i, i0, i1
151  integer(I4B) :: l, u, lp, up
152  real(DP) :: t
153 
154  ! by default, need to iterate over all times
155  i0 = 1
156  i1 = size(this%times)
157 
158  ! if no times fall within the slice, set to [-1, -1]
159  l = -1
160  u = -1
161 
162  ! previous bounding indices
163  lp = this%selection(1)
164  up = this%selection(2)
165 
166  ! Check if we can reuse either the lower or upper bound.
167  ! The lower doesn't need to change if it indexes the first
168  ! time strictly after the slice's start (i.e., the previous
169  ! time is at or before t0, and this time is after t0).
170  ! The upper doesn't need to change if it indexes the last
171  ! time at or before the slice's end.
172  if (lp > 0 .and. up > 0) then
173  if (lp > 1) then
174  if (this%times(lp - 1) <= t0 .and. &
175  this%times(lp) > t0) then
176  l = lp
177  i0 = l
178  end if
179  end if
180  if (up > 1 .and. up < i1) then
181  if (this%times(up + 1) > t1 .and. &
182  this%times(up) <= t1) then
183  u = up
184  i1 = u
185  end if
186  end if
187  if (l == lp .and. u == up) then
188  this%selection = (/l, u/)
189  if (present(changed)) changed = .false.
190  return
191  end if
192  end if
193 
194  ! recompute bounding indices if needed
195  do i = i0, i1
196  t = this%times(i)
197  if (l < 0 .and. t > t0 .and. t <= t1) l = i
198  if (l > 0 .and. t <= t1) u = i
199  end do
200  this%selection = (/l, u/)
201  if (present(changed)) changed = l /= lp .or. u /= up
202 
203  end subroutine
204 
205  !> @brief Update the selection to the current time step.
206  subroutine advance(this)
207  ! modules
208  use tdismodule, only: kper, kstp, nper, nstp, totimc, delt
209  ! dummy
210  class(timeselecttype) :: this
211  ! local
212  real(DP) :: l, u
213 
214  if (kper == 1 .and. kstp == 1) then
215  ! For first time step, use a small negative lower bound
216  ! capture times at t=0.0 despite exclusive lower bound
217  l = -epsilon(dzero)
218  else
219  l = totimc
220  end if
221  if (kper == nper .and. kstp == nstp(kper)) then
222  ! For last time step, use a large upper bound to
223  ! capture times beyond the end of the simulation
224  u = huge(done)
225  else
226  u = totimc + delt
227  end if
228  call this%select(l, u)
229  end subroutine advance
230 
231  !> @brief Check if any times are currently selected.
232  !!
233  !! Indicates whether any times are selected for the
234  !! current time step.
235  !!
236  !! Note that this routine does NOT indicate whether
237  !! the times array has nonzero size; use the size
238  !! intrinsic for that.
239  !<
240  function any(this) result(a)
241  class(timeselecttype) :: this
242  logical(LGP) :: a
243 
244  a = all(this%selection > 0)
245  end function any
246 
247  !> @brief Return the number of times currently selected.
248  !!
249  !! Returns the number of times selected for the current
250  !! time step.
251  !!
252  !! Note that this routine does NOT return the total size
253  !! of the times array; use the size intrinsic for that.
254  !<
255  function count(this) result(n)
256  class(timeselecttype) :: this
257  integer(I4B) :: n
258 
259  if (this%any()) then
260  n = this%selection(2) - this%selection(1)
261  else
262  n = 0
263  end if
264  end function count
265 
266  !> @brief Sort the time selection in increasing order.
267  !!
268  !! Note that this routine does NOT remove duplicate times.
269  !! Call increasing() to check for duplicates in the array.
270  !<
271  subroutine sort(this)
272  class(timeselecttype) :: this
273  integer(I4B), allocatable :: indx(:)
274 
275  allocate (indx(size(this%times)))
276  call qsort(indx, this%times)
277  deallocate (indx)
278  end subroutine sort
279 
280  !> @brief Extend the time selection with the given array.
281  !!
282  !! This routine sorts the selection after appending the
283  !! elements of the given array, but users should likely
284  !! still call increasing() to check for duplicate times.
285  !<
286  subroutine extend(this, a)
287  class(timeselecttype) :: this
288  real(DP) :: a(:)
289 
290  this%times = [this%times, a]
291  call this%sort()
292  end subroutine extend
293 
294  !> @brief Check whether any configured time is within tolerance of t.
295  !!
296  !! Unlike any()/select(), this checks the full array of times, not just
297  !! the current time step's slice. Useful for deduplicating: times may
298  !! come from multiple sources (e.g. period block configuration and an
299  !! explicitly specified set of release times).
300  !<
301  function contains_close(this, t, tolerance) result(found)
302  class(timeselecttype) :: this
303  real(dp), intent(in) :: t
304  real(dp), intent(in) :: tolerance
305  logical(LGP) :: found
306  integer(I4B) :: i
307 
308  found = .false.
309  if (.not. allocated(this%times)) return
310  do i = 1, size(this%times)
311  if (is_close(this%times(i), t, atol=tolerance)) then
312  found = .true.
313  return
314  end if
315  end do
316  end function contains_close
317 
318 end module timeselectmodule
subroutine init()
Definition: GridSorting.f90:25
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
This module defines variable data types.
Definition: kind.f90:8
pure logical function, public is_close(a, b, rtol, atol, symmetric)
Check if a real value is approximately equal to another.
Definition: MathUtil.f90:46
integer(i4b), dimension(:), pointer, public, contiguous nstp
number of time steps in each stress period
Definition: tdis.f90:42
real(dp), pointer, public totimc
simulation time at start of time step
Definition: tdis.f90:36
integer(i4b), pointer, public kstp
current time step number
Definition: tdis.f90:27
integer(i4b), pointer, public kper
current stress period number
Definition: tdis.f90:26
real(dp), pointer, public delt
length of the current time step
Definition: tdis.f90:32
integer(i4b), pointer, public nper
number of stress period
Definition: tdis.f90:24
Specify times for some event to occur.
Definition: TimeSelect.f90:2
logical(lgp) function increasing(this)
Determine if times strictly increase.
Definition: TimeSelect.f90:89
subroutine advance(this)
Update the selection to the current time step.
Definition: TimeSelect.f90:207
integer(i4b) function count(this)
Return the number of times currently selected.
Definition: TimeSelect.f90:256
subroutine log(this, iout, verb)
Show the current time selection, if any.
Definition: TimeSelect.f90:110
subroutine deallocate(this)
Deallocate the time selection object.
Definition: TimeSelect.f90:57
logical(lgp) function contains_close(this, t, tolerance)
Check whether any configured time is within tolerance of t.
Definition: TimeSelect.f90:302
logical(lgp) function any(this)
Check if any times are currently selected.
Definition: TimeSelect.f90:241
subroutine sort(this)
Sort the time selection in increasing order.
Definition: TimeSelect.f90:272
subroutine expand(this, increment)
Expand capacity by the given amount. Resets the current slice.
Definition: TimeSelect.f90:63
subroutine select(this, t0, t1, changed)
Select times in the interval (t0, t1] (exclusive lower, inclusive upper).
Definition: TimeSelect.f90:145
subroutine extend(this, a)
Extend the time selection with the given array.
Definition: TimeSelect.f90:287
Represents a series of instants at which some event should occur.
Definition: TimeSelect.f90:35