Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
time_step_controller.f90
Go to the documentation of this file.
1! Copyright (c) 2022, 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 logger, only : neko_log, log_size
37 use utils, only : neko_error
38 use json_module, only : json_file
40 use time_state, only : time_state_t
41 use math, only : abscmp
42 use comm, only : neko_global_comm, is_mpmd
43 use mpi_f08, only : mpi_min, mpi_max, mpi_in_place, mpi_allreduce, &
44 mpi_double_precision, mpi_integer
45 implicit none
46 private
47
51 real(kind=dp), public, parameter :: landing_tol = 1.0e-6_dp
52
54 real(kind=dp), parameter :: max_landing_steps = 1.0e15_dp
55
57 type, public :: time_step_controller_t
58 logical :: is_variable_dt = .false.
59 real(kind=dp) :: cfl_trg = 0.0_dp
60 real(kind=dp) :: cfl_avg = 0.0_dp
61 real(kind=dp) :: init_dt = huge(0.0_dp)
62 real(kind=dp) :: max_dt = 0.0_dp
63 real(kind=dp) :: min_dt = 0.0_dp
64 integer :: max_update_frequency = 0
65 integer :: min_update_frequency = 0
68 integer :: dt_last_change = -1
69 real(kind=dp) :: alpha = 0.0_dp
70 real(kind=dp) :: max_dt_increase_factor = 0.0_dp
71 real(kind=dp) :: min_dt_decrease_factor = 0.0_dp
72 real(kind=dp) :: dev_tol = 0.0_dp
75 logical :: exact_output_time = .false.
77 real(kind=dp) :: dt_previous = 0.0_dp
78 contains
80 procedure, pass(this) :: init => time_step_controller_init
82 procedure, pass(this) :: set_dt => time_step_controller_set_dt
84 procedure, pass(this) :: first_dt => time_step_controller_first_dt
86 procedure, pass(this) :: land => time_step_controller_land
87
89
90contains
91
94 subroutine time_step_controller_init(this, params)
95 class(time_step_controller_t), intent(inout) :: this
96 type(json_file), intent(inout) :: params
97 integer :: flags_any(2), flags_all(2), ierr
98
99 this%dt_last_change = -1
100 call json_get_or_default(params, 'variable_timestep', &
101 this%is_variable_dt, .false.)
102 if (this%is_variable_dt) then
103 call json_get_or_lookup_or_default(params, 'target_cfl', &
104 this%cfl_trg, 0.4_dp)
105 call json_get_or_lookup_or_default(params, 'timestep', &
106 this%init_dt, huge(0.0_dp))
107 call json_get_or_lookup_or_default(params, 'max_timestep', &
108 this%max_dt, huge(0.0_dp))
109 call json_get_or_lookup_or_default(params, 'min_timestep', &
110 this%min_dt, 0.0_dp)
111 call json_get_or_lookup_or_default(params, 'max_update_frequency',&
112 this%max_update_frequency, 0)
113 call json_get_or_lookup_or_default(params, 'min_update_frequency',&
114 this%min_update_frequency, huge(0))
115 call json_get_or_lookup_or_default(params, 'cfl_running_avg_coeff', &
116 this%alpha, 0.5_dp)
117 call json_get_or_lookup_or_default(params, 'max_dt_increase_factor', &
118 this%max_dt_increase_factor, 1.2_dp)
119 call json_get_or_lookup_or_default(params, 'min_dt_decrease_factor', &
120 this%min_dt_decrease_factor, 0.5_dp)
121 call json_get_or_lookup_or_default(params, 'cfl_deviation_tolerance', &
122 this%dev_tol, 0.2_dp)
123 end if
124
125 call json_get_or_default(params, 'exact_output_time', &
126 this%exact_output_time, .false.)
127 if (this%exact_output_time .and. .not. this%is_variable_dt) then
128 call neko_error('exact_output_time requires variable_timestep')
129 end if
130
131 ! A variable time step takes a collective at every step in an MPMD run,
132 ! and the landing on the scheduled times another one, so the coupled
133 ! simulations have to agree on both, or their collectives would not
134 ! match up.
135 if (is_mpmd()) then
136 flags_any = merge(1, 0, [this%is_variable_dt, this%exact_output_time])
137 flags_all = flags_any
138 call mpi_allreduce(mpi_in_place, flags_any, 2, mpi_integer, &
139 mpi_max, neko_global_comm, ierr)
140 call mpi_allreduce(mpi_in_place, flags_all, 2, mpi_integer, &
141 mpi_min, neko_global_comm, ierr)
142 if (any(flags_any .ne. flags_all)) then
143 call neko_error('variable_timestep and exact_output_time must &
144 &each be set in all the coupled cases of an MPMD run, or in &
145 &none')
146 end if
147 end if
148
149 end subroutine time_step_controller_init
150
157 pure function time_step_controller_first_dt(this, time, cfl) result(dt)
158 class(time_step_controller_t), intent(in) :: this
159 type(time_state_t), intent(in) :: time
160 real(kind=dp), intent(in) :: cfl
161 real(kind=dp) :: dt
162
163 dt = this%init_dt
164 if (cfl .gt. 0.0_dp) dt = min(this%cfl_trg / cfl * time%dt, dt)
165 dt = max(min(dt, this%max_dt), this%min_dt)
166
168
181 subroutine time_step_controller_set_dt(this, time, cfl)
182 class(time_step_controller_t), intent(inout) :: this
183 type(time_state_t), intent(inout) :: time
184 real(kind=dp), intent(in) :: cfl
185 real(kind=dp) :: dt_old, scaling_factor, global_min_dt
186 character(len=LOG_SIZE) :: log_buf
187 integer :: ierr
188
189 this%dt_previous = time%dt
190
191 ! Check if variable dt is requested
192 if (.not. this%is_variable_dt) return
193
194 ! Reset the average cfl if it is the first time step since the last change
195 if (this%dt_last_change .eq. 0) then
196 this%cfl_avg = cfl
197 end if
198
199 if (this%dt_last_change .eq. -1) then
200
201 ! Set the first dt for desired cfl, or use the provided initial dt if it
202 ! is smaller. Then clamp between max and min dt if provided.
203 time%dt = this%first_dt(time, cfl)
204 this%dt_last_change = 0
205 this%cfl_avg = cfl
206 ! There is no step before the first one
207 this%dt_previous = time%dt
208
209 else
210 ! Calculate the average of cfl over the desired interval
211 this%cfl_avg = this%alpha * cfl + (1 - this%alpha) * this%cfl_avg
212
213 if (abs(this%cfl_avg - this%cfl_trg) .ge. this%dev_tol * this%cfl_trg &
214 .and. this%dt_last_change .ge. this%max_update_frequency &
215 .or. this%dt_last_change .ge. this%min_update_frequency) then
216
217 ! Test for an exact zero cfl first, so that it is never divided by.
218 if (abscmp(cfl, 0.0_dp)) then
219 ! no limit on dt, so increase it as much as allowed
220 scaling_factor = this%max_dt_increase_factor
221 else if (this%cfl_trg/cfl .ge. 1) then
222 ! increase of time step
223 scaling_factor = min(this%max_dt_increase_factor, this%cfl_trg/cfl)
224 else
225 ! reduction of time step
226 scaling_factor = max(this%min_dt_decrease_factor, this%cfl_trg/cfl)
227 end if
228
229 dt_old = time%dt
230 time%dt = scaling_factor * dt_old
231 time%dt = max(min(time%dt, this%max_dt), this%min_dt)
232
233 write(log_buf, '(A,E15.7,1x,A,E15.7)') &
234 'Average CFL:', this%cfl_avg, &
235 'Target CFL:', this%cfl_trg
236 call neko_log%message(log_buf)
237
238 write(log_buf, '(A,E15.7,1x,A,E15.7)') 'Old dt:', dt_old, &
239 'New dt:', time%dt
240 call neko_log%message(log_buf)
241
242 this%dt_last_change = 0
243
244 else
245 this%dt_last_change = this%dt_last_change + 1
246 end if
247 end if
248
249 ! If running in mpmd, the new dt is the minimum across simulations
250 if (is_mpmd()) then
251 global_min_dt = time%dt
252 call mpi_allreduce(mpi_in_place, global_min_dt, 1, &
253 mpi_double_precision, mpi_min, neko_global_comm, ierr)
254
255 ! If my dt is larger that the global min, mark a change
256 if (time%dt .gt. global_min_dt) then
257 time%dt = global_min_dt
258 this%dt_last_change = 0
259 end if
260 end if
261
262
263 end subroutine time_step_controller_set_dt
264
286 subroutine time_step_controller_land(this, time, time_to_next)
287 class(time_step_controller_t), intent(inout) :: this
288 type(time_state_t), intent(inout) :: time
289 real(kind=dp), intent(in) :: time_to_next
290 real(kind=dp) :: direction, dt_prev, lo, remaining, remaining_end, x
291 integer(kind=i8) :: n
292 integer :: ierr
293
294 if (.not. (this%exact_output_time .and. this%is_variable_dt)) return
295
296 direction = sign(1.0_dp, time%dt)
297 dt_prev = abs(this%dt_previous)
298 lo = this%min_dt_decrease_factor * dt_prev
299
300 ! The end, or the next scheduled time unless it is too close
301 remaining_end = direction * (time%end_time - time%t)
302 if (remaining_end .le. time_to_next + lo) then
303 remaining = remaining_end
304 else if (time_to_next .ge. lo) then
305 remaining = time_to_next
306 else
307 remaining = 0.0_dp
308 end if
309
310 x = remaining / abs(time%dt)
311 if (remaining .gt. 0.0_dp .and. x .lt. max_landing_steps) then
312 ! Candidate count: x proposed steps fit into the remaining time,
313 ! round down, but take at least one step
314 n = max(1_i8, floor(x, kind = i8))
315 ! Round up instead if x is closer to n + 1 in ratio, that is, if
316 ! (n + 1) / x < x / n. The boundary is the geometric mean
317 ! sqrt(n (n + 1)), not n + 0.5, so that a step twice too long and one
318 ! half as long count as equally far off
319 if (x * x .gt. real(n, dp) * real(n + 1_i8, dp)) n = n + 1_i8
320 ! Lower bound on the step, min_dt_decrease_factor times the previous
321 ! one: at most remaining / lo steps, and at least one. The tolerance
322 ! keeps round-off from dropping a count that fits exactly
323 n = min(n, max(1_i8, floor(remaining / lo + landing_tol, kind = i8)))
324 ! Upper bound on the step, max_dt_increase_factor times the previous
325 ! one or max_timestep, whichever is smaller: at least that many
326 ! steps. Applied last, so it wins if the two bounds conflict
327 n = max(n, ceiling(remaining / min(this%max_dt_increase_factor * &
328 dt_prev, this%max_dt) - landing_tol, kind = i8))
329 ! The fitted step: n equal steps end exactly on the target
330 time%dt = direction * remaining / real(n, dp)
331 end if
332
333 ! In an MPMD run the simulations advance in lockstep
334 if (is_mpmd()) then
335 call mpi_allreduce(mpi_in_place, time%dt, 1, mpi_double_precision, &
336 mpi_min, neko_global_comm, ierr)
337 end if
338
339 ! The projection spaces watch for a change of the step taken
340 if (abs(abs(time%dt) - dt_prev) .gt. landing_tol * dt_prev) then
341 this%dt_last_change = 0
342 end if
343
344 end subroutine time_step_controller_land
345
346end module time_step_controller
double real
Retrieves a parameter by name or assigns a provided default value. In the latter case also adds the m...
Definition comm.F90:1
type(mpi_comm), public neko_global_comm
Definition comm.F90:47
pure logical function, public is_mpmd()
Whether this is one of several simulations coupled in an MPMD run, that is, whether Neko's communicat...
Definition comm.F90:83
Utilities for retrieving parameters from the case files.
Logging routines.
Definition log.f90:34
type(log_t), public neko_log
Global log stream.
Definition log.f90:92
integer, parameter, public log_size
Definition log.f90:46
Definition math.F90:60
integer, parameter, public i8
Definition num_types.f90:7
integer, parameter, public dp
Definition num_types.f90:10
Module with things related to the simulation time.
Implements type time_step_controller.
real(kind=dp), parameter, public landing_tol
Relative tolerance on the number of steps up to a scheduled time, so that round-off in the accumulate...
subroutine time_step_controller_init(this, params)
Constructor.
real(kind=dp), parameter max_landing_steps
Largest number of steps up to a scheduled time that is still fitted.
pure real(kind=dp) function time_step_controller_first_dt(this, time, cfl)
The first time step of a variable time step run: the one giving the target CFL number,...
subroutine time_step_controller_land(this, time, time_to_next)
Fit the time step so that the next scheduled time, or the end time, is reached exactly.
subroutine time_step_controller_set_dt(this, time, cfl)
Set new dt based on cfl if requested.
Utilities.
Definition utils.f90:35
A struct that contains all info about the time, expand as needed.
#define max(a, b)
Definition tensor.cu:40