Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
time_based_controller.f90
Go to the documentation of this file.
1! Copyright (c) 2023, The Neko Authors
2! All rights reserved.
3!
4! Redistribution and use in source and binary forms, with or without
5! modification, are permitted provided that the following conditions
6! are met:
7!
8! * Redistributions of source code must retain the above copyright
9! notice, this list of conditions and the following disclaimer.
10!
11! * Redistributions in binary form must reproduce the above
12! copyright notice, this list of conditions and the following
13! disclaimer in the documentation and/or other materials provided
14! with the distribution.
15!
16! * Neither the name of the authors nor the names of its
17! contributors may be used to endorse or promote products derived
18! from this software without specific prior written permission.
19!
20! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24! COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31! POSSIBILITY OF SUCH DAMAGE.
32!
35 use num_types, only : dp, i8
36 use utils, only : neko_error
37 use time_state, only : time_state_t
38 implicit none
39 private
40
49 real(kind=dp), public, parameter :: time_tol = 0.1_dp
50
53 real(kind=dp), public, parameter :: span_tol = 1.0e-9_dp
54
88 real(kind=dp) :: frequency = 0.0_dp
90 real(kind=dp) :: time_interval = 0.0_dp
92 integer :: nsteps = 0
94 real(kind=dp) :: start_time = 0.0_dp
96 real(kind=dp) :: end_time = 0.0_dp
98 integer :: nexecutions = 0
100 logical :: never = .false.
103 character(len=:), allocatable :: control_mode
105 real(kind=dp) :: control_value = 0.0_dp
108 real(kind=dp) :: anchor_time = 0.0_dp
113 logical :: write_at_start = .true.
115 integer(kind=i8) :: first_index = 0
117 logical :: start_is_scheduled = .false.
121 logical :: start_pending = .false.
124 integer :: next_index = 0
127 real(kind=dp) :: direction = 1.0_dp
130 integer :: tstep_offset = 0
134 integer :: last_tstep = -1
135
136 contains
138 procedure, pass(this) :: init => time_based_controller_init
140 procedure, pass(this) :: free => time_based_controller_free
142 procedure, pass(this) :: check => time_based_controller_check
144 procedure, pass(this) :: register_execution => &
147 procedure, pass(this) :: set_counter => &
150 procedure, pass(this) :: next_time => time_based_controller_next_time
153 procedure, pass(this) :: time_to_next => &
156 procedure, pass(this) :: tolerance => time_based_controller_tolerance
158
159contains
160
176 subroutine time_based_controller_init(this, start_time, end_time, &
177 control_mode, control_value, write_at_start, anchor_time, direction)
178 class(time_based_controller_t), intent(inout) :: this
179 real(kind=dp), intent(in) :: start_time
180 real(kind=dp), intent(in) :: end_time
181 character(len=*), intent(in) :: control_mode
182 real(kind=dp), intent(in) :: control_value
183 logical, intent(in), optional :: write_at_start
184 real(kind=dp), intent(in), optional :: anchor_time
185 real(kind=dp), intent(in), optional :: direction
186 real(kind=dp) :: span, offset
187
188 call this%free()
189
190 this%start_time = start_time
191 this%end_time = end_time
192 this%control_mode = control_mode
193 this%control_value = control_value
194
195 if (present(write_at_start)) then
196 this%write_at_start = write_at_start
197 else
198 this%write_at_start = .true.
199 end if
200
201 if (present(direction)) then
202 this%direction = sign(1.0_dp, direction)
203 else
204 this%direction = sign(1.0_dp, end_time - start_time)
205 end if
206 ! Length of the interval covered, positive also for a run backwards.
207 span = this%direction * (end_time - start_time)
208
209 if (trim(control_mode) .eq. 'simulationtime') then
210 if (control_value .le. 0.0_dp) then
211 call neko_error("The output interval must be positive")
212 end if
213 this%time_interval = control_value
214 this%frequency = 1.0_dp / this%time_interval
215 this%nsteps = 0
216 else if (trim(control_mode) .eq. 'nsamples') then
217 if (control_value .le. 0.0_dp) then
218 call neko_error("nsamples must be positive")
219 end if
220 if (span .le. 0.0_dp) then
221 call neko_error("nsamples requires the output to start before the &
222 &end of the simulation")
223 end if
224
225 this%frequency = control_value / span
226 this%time_interval = 1.0_dp / this%frequency
227 this%nsteps = 0
228 ! The samples divide the interval from start_time to end_time, so
229 ! they are counted from start_time.
230 this%anchor_time = start_time
231 else if (trim(control_mode) .eq. 'tsteps') then
232 if (control_value .lt. 1.0_dp) then
233 call neko_error("The output interval in time steps must be at &
234 &least 1")
235 end if
236 this%nsteps = int(control_value)
237 ! if the timestep will be variable, we cannot compute these.
238 this%frequency = 0.0_dp
239 this%time_interval = 0.0_dp
240 else if (trim(control_mode) .eq. 'never') then
241 this%never = .true.
242 else
243 call neko_error("The control parameter must be simulationtime, nsamples&
244 & tsteps, or never, but received "//trim(control_mode))
245 end if
246
247 if (present(anchor_time)) this%anchor_time = anchor_time
248
249 ! Find the index of the first scheduled time, and whether start_time is
250 ! itself one of the scheduled times.
251 this%first_index = 0
252 this%start_is_scheduled = .true.
253 this%start_pending = .false.
254
255 if (this%time_interval .gt. 0.0_dp) then
256 offset = this%direction * (start_time - this%anchor_time) / &
257 this%time_interval
258 this%first_index = ceiling(offset - span_tol * max(1.0_dp, &
259 abs(offset)), kind = i8)
260 this%start_is_scheduled = abs(real(this%first_index, dp) - offset) &
261 .le. span_tol * max(1.0_dp, abs(offset))
262 if (this%start_is_scheduled .and. .not. this%write_at_start) then
263 this%first_index = this%first_index + 1_i8
264 end if
265 this%start_pending = this%write_at_start .and. &
266 .not. this%start_is_scheduled
267 end if
268
269 if (this%nsteps .gt. 0 .and. .not. this%write_at_start) then
270 ! The first scheduled step of a step based schedule is the first step
271 ! of the run. Skip it, as start_time is skipped above.
272 this%next_index = 1
273 else
274 this%next_index = 0
275 end if
276
277 end subroutine time_based_controller_init
278
281 class(time_based_controller_t), intent(inout) :: this
282
283 if (allocated(this%control_mode)) then
284 deallocate(this%control_mode)
285 end if
286
287 this%frequency = 0.0_dp
288 this%time_interval = 0.0_dp
289 this%nsteps = 0
290 this%start_time = 0.0_dp
291 this%end_time = 0.0_dp
292 this%nexecutions = 0
293 this%never = .false.
294 this%control_value = 0.0_dp
295 this%write_at_start = .true.
296 this%anchor_time = 0.0_dp
297 this%first_index = 0
298 this%start_is_scheduled = .false.
299 this%start_pending = .false.
300 this%next_index = 0
301 this%direction = 1.0_dp
302 this%tstep_offset = 0
303 this%last_tstep = -1
304 end subroutine time_based_controller_free
305
318 pure function time_based_controller_check(this, time, force) &
319 result(check)
320 class(time_based_controller_t), intent(in) :: this
321 type(time_state_t), intent(in) :: time
322 logical, intent(in), optional :: force
323 logical :: check
324 logical :: ifforce
325 real(kind=dp) :: progress, tol, t_next, t_start, t_end
326 integer :: nstep
327
328 if (present(force)) then
329 ifforce = force
330 else
331 ifforce = .false.
332 end if
333
334 check = .false.
335
336 ! At most one execution per time step.
337 if (this%last_tstep .eq. time%tstep) return
338
339 ! Nothing is scheduled, but an execution can still be forced.
340 if (this%never .and. .not. ifforce) return
341
342 progress = this%direction * (time%t - this%anchor_time)
343 t_start = this%direction * (this%start_time - this%anchor_time)
344 tol = this%tolerance(time%dt)
345
346 ! Nothing is executed before start_time.
347 if (progress .lt. t_start - tol) return
348
349 if (ifforce) then
350 check = .true.
351 else if (this%nsteps .gt. 0) then
352 nstep = time%tstep - this%tstep_offset
353 check = nstep .ge. this%next_index * this%nsteps
354 else if (this%start_pending) then
355 ! The execution at start_time, which is not one of the scheduled
356 ! times.
357 check = .true.
358 else
359 t_end = this%direction * (this%end_time - this%anchor_time)
360 t_next = real(this%first_index + this%next_index, dp) * &
361 this%time_interval
362 ! Nothing is scheduled after end_time. The condition is on the
363 ! scheduled time and not on the current time, so a step that
364 ! overshoots end_time still performs the execution scheduled for it.
365 if (t_next .gt. t_end + span_tol * max(abs(t_end), &
366 this%time_interval)) return
367 check = progress .ge. t_next - tol
368 end if
369
370 end function time_based_controller_check
371
378 pure function next_index_after(this, time) result(index)
379 class(time_based_controller_t), intent(in) :: this
380 type(time_state_t), intent(in) :: time
381 integer :: index
382 real(kind=dp) :: progress, tol
383
384 if (this%nsteps .gt. 0) then
385 index = (time%tstep - this%tstep_offset) / this%nsteps + 1
386 else if (this%time_interval .gt. 0.0_dp) then
387 progress = this%direction * (time%t - this%anchor_time)
388 tol = this%tolerance(time%dt)
389 index = int(floor((progress + tol) / this%time_interval, kind = i8) &
390 - this%first_index) + 1
391 else
392 ! Nothing is scheduled.
393 index = this%next_index
394 end if
395
396 ! The result is not limited from below by the current `next_index` on
397 ! purpose: a scheduled execution always gives a larger index, and a
398 ! forced execution before the next scheduled time must leave
399 ! `next_index` unchanged.
400 index = max(index, 0)
401
402 end function next_index_after
403
407 class(time_based_controller_t), intent(inout) :: this
408 type(time_state_t), intent(in) :: time
409
410 this%nexecutions = this%nexecutions + 1
411 this%start_pending = .false.
412 this%next_index = next_index_after(this, time)
413 this%last_tstep = time%tstep
414
416
425 class(time_based_controller_t), intent(inout) :: this
426 type(time_state_t), intent(in) :: time
427 real(kind=dp) :: progress, tol, dt, t_start, t_first
428 integer :: n_passed
429
430 if (this%never) return
431
432 if (this%nsteps .gt. 0) then
433 ! `tstep` is not stored in the checkpoint, so a step based schedule
434 ! cannot be continued. Start a new one from the restart step, without
435 ! executing at the restart step itself.
436 this%tstep_offset = time%tstep
437 this%next_index = 1
438 this%start_pending = .false.
439 this%last_tstep = time%tstep
440 return
441 end if
442
443 ! The size of the step that produced the checkpoint, which is the step
444 ! `check` used at that time, and not the step of the new run.
445 dt = time%dt
446 if (abs(time%dtlag(1)) .gt. 0.0_dp) dt = time%dtlag(1)
447
448 progress = this%direction * (time%t - this%anchor_time)
449 t_start = this%direction * (this%start_time - this%anchor_time)
450 tol = this%tolerance(dt)
451
452 if (progress .lt. t_start - tol) then
453 ! The run has not reached start_time.
454 this%next_index = 0
455 this%nexecutions = 0
456 else
457 n_passed = int(floor((progress + tol) / this%time_interval, &
458 kind = i8) - this%first_index) + 1
459 this%next_index = max(n_passed, 0)
460 this%nexecutions = this%next_index
461 if (this%start_pending) then
462 ! The execution at start_time was performed by the previous run.
463 ! It is counted separately unless the first scheduled time was
464 ! within tolerance of start_time, in which case the same execution
465 ! was registered for both (see next_index_after).
466 t_first = real(this%first_index, dp) * this%time_interval
467 if (t_first .gt. t_start + tol) then
468 this%nexecutions = this%nexecutions + 1
469 end if
470 end if
471 this%start_pending = .false.
472 end if
473
474 ! Anything scheduled for the restart time was executed by the run that
475 ! wrote the checkpoint.
476 this%last_tstep = time%tstep
477
479
483 pure function time_based_controller_tolerance(this, dt) result(tol)
484 class(time_based_controller_t), intent(in) :: this
485 real(kind=dp), intent(in) :: dt
486 real(kind=dp) :: tol
487
488 tol = time_tol * abs(dt)
489 if (this%time_interval .gt. 0.0_dp) then
490 tol = min(tol, time_tol * this%time_interval)
491 end if
492
494
498 pure function time_based_controller_next_time(this) result(t)
499 class(time_based_controller_t), intent(in) :: this
500 real(kind=dp) :: t
501
502 if (this%never .or. this%nsteps .gt. 0) then
503 t = this%end_time
504 else if (this%start_pending) then
505 t = this%start_time
506 else
507 t = this%anchor_time + this%direction * &
508 real(this%first_index + this%next_index, dp) * this%time_interval
509 end if
510
512
521 pure function time_based_controller_time_to_next(this, time) &
522 result(t_to_next)
523 class(time_based_controller_t), intent(in) :: this
524 type(time_state_t), intent(in) :: time
525 real(kind=dp) :: t_to_next
526 real(kind=dp) :: progress, tol, t_next, t_end
527 integer(kind=i8) :: k
528
529 t_to_next = huge(0.0_dp)
530
531 ! A step based or disabled control has no scheduled times: it executes
532 ! after a number of steps, or only when forced, wherever the steps end.
533 if (this%never .or. this%nsteps .gt. 0) return
534
535 ! All times are measured as progress from the anchor in the direction of
536 ! the run, so that they increase also for a run backwards in time. The
537 ! tolerance only absorbs round-off in the accumulated time.
538 progress = this%direction * (time%t - this%anchor_time)
539 tol = span_tol * max(abs(progress), this%time_interval)
540
541 if (this%start_pending) then
542 ! The execution at start_time is still to come. It is not one of the
543 ! scheduled times, e.g. for a component with a start_time of its own
544 ! between two of them, so it is landed on separately.
545 t_next = this%direction * (this%start_time - this%anchor_time)
546 else
547 ! The next scheduled time that has not been executed yet.
548 t_next = real(this%first_index + this%next_index, dp) * &
549 this%time_interval
550 end if
551
552 if (t_next .le. progress + tol) then
553 ! The execution is due already but has not been performed, which
554 ! happens at the start of the run for the simulation components: they
555 ! are not executed before the time loop. It is performed at the end of
556 ! the coming step, wherever that step ends, so the step is fitted to
557 ! the first scheduled time after the current time instead.
558 k = max(this%first_index, &
559 floor((progress + tol) / this%time_interval, kind = i8) + 1_i8)
560 t_next = real(k, dp) * this%time_interval
561 end if
562
563 ! A scheduled time after end_time is never executed. The end itself is
564 ! landed on by the time step controller.
565 t_end = this%direction * (this%end_time - this%anchor_time)
566 if (t_next .gt. t_end + span_tol * max(abs(t_end), this%time_interval)) &
567 return
568
569 t_to_next = t_next - progress
570
572
573end module time_based_controller
double real
integer, parameter, public i8
Definition num_types.f90:7
integer, parameter, public dp
Definition num_types.f90:10
Contains the time_based_controller_t type.
subroutine time_based_controller_free(this)
Destructor.
subroutine time_based_controller_set_counter(this, time)
Set next_index to the first scheduled time not yet reached, and nexecutions to the number of executio...
pure real(kind=dp) function time_based_controller_time_to_next(this, time)
The time until the next scheduled execution that the time step should land on, or huge(0....
pure integer function next_index_after(this, time)
The number of scheduled times at or before the given time, counted from first_index....
pure logical function time_based_controller_check(this, time, force)
Check if the execution should be performed.
pure real(kind=dp) function time_based_controller_next_time(this)
The time of the next scheduled execution.
subroutine time_based_controller_register_execution(this, time)
Increment nexecutions and advance next_index past the current time.
pure real(kind=dp) function time_based_controller_tolerance(this, dt)
The tolerance used when comparing the simulation time to a scheduled execution time.
real(kind=dp), parameter, public span_tol
Relative tolerance for round-off in k * time_interval when comparing a scheduled time to start_time a...
subroutine time_based_controller_init(this, start_time, end_time, control_mode, control_value, write_at_start, anchor_time, direction)
Constructor.
real(kind=dp), parameter, public time_tol
Tolerance for comparing the simulation time to a scheduled time, as a fraction of the time step....
Module with things related to the simulation time.
Utilities.
Definition utils.f90:35
A utility type for determining whether an action should be executed based on the current time value....
A struct that contains all info about the time, expand as needed.
#define max(a, b)
Definition tensor.cu:40