Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/callbacks_step/stepsize.jl
5586 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
"""
9
StepsizeCallback(; cfl=1.0, cfl_parabolic = 0.0,
10
interval = 1)
11
12
Set the time step size according to a CFL condition with hyperbolic CFL number `cfl`
13
if the time integration method isn't adaptive itself.
14
The hyperbolic CFL number `cfl` must be either a `Real` number, corresponding to a constant
15
CFL number, or a function of time `t` returning a `Real` number.
16
The latter approach allows for variable CFL numbers that can be used to realize, e.g.,
17
a ramp-up of the time step.
18
19
One can additionally supply a parabolic CFL number `cfl_parabolic` to
20
limit the admissible timestep also respecting parabolic restrictions.
21
This is only applicable for semidiscretizations of type
22
[`SemidiscretizationHyperbolicParabolic`](@ref) and [`SemidiscretizationParabolic`](@ref).
23
To enable checking for parabolic timestep restrictions, provide a value greater than zero for `cfl_parabolic`.
24
By default, `cfl_parabolic` is set to zero which means that only the hyperbolic CFL number `cfl` is considered.
25
The keyword argument `cfl_parabolic` must be either a `Real` number, corresponding to a constant
26
parabolic CFL number, or a function of time `t` returning a `Real` number.
27
28
By default, the timestep will be adjusted at every step.
29
For different values of `interval`, the timestep will be adjusted every `interval` steps.
30
"""
31
struct StepsizeCallback{CflHyperbolicType, CflParabolicType}
32
cfl_hyperbolic::CflHyperbolicType
33
cfl_parabolic::CflParabolicType
34
interval::Int
35
end
36
37
function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:StepsizeCallback})
38
@nospecialize cb # reduce precompilation time
39
40
stepsize_callback = cb.affect!
41
@unpack cfl_hyperbolic, cfl_parabolic, interval = stepsize_callback
42
print(io, "StepsizeCallback(",
43
"cfl_hyperbolic=", cfl_hyperbolic, ", ",
44
"cfl_parabolic=", cfl_parabolic, ", ",
45
"interval=", interval, ")")
46
return nothing
47
end
48
49
function Base.show(io::IO, ::MIME"text/plain",
50
cb::DiscreteCallback{<:Any, <:StepsizeCallback})
51
@nospecialize cb # reduce precompilation time
52
53
if get(io, :compact, false)
54
show(io, cb)
55
else
56
stepsize_callback = cb.affect!
57
58
setup = [
59
"CFL Hyperbolic" => stepsize_callback.cfl_hyperbolic,
60
"CFL Parabolic" => stepsize_callback.cfl_parabolic,
61
"Interval" => stepsize_callback.interval
62
]
63
summary_box(io, "StepsizeCallback", setup)
64
end
65
end
66
67
function StepsizeCallback(; cfl = 1.0, cfl_parabolic = 0.0,
68
interval = 1)
69
# Convert plain real numbers to functions for unified treatment
70
cfl_hyp = isa(cfl, Real) ? Returns(cfl) : cfl
71
cfl_para = isa(cfl_parabolic, Real) ? Returns(cfl_parabolic) : cfl_parabolic
72
stepsize_callback = StepsizeCallback{typeof(cfl_hyp), typeof(cfl_para)}(cfl_hyp,
73
cfl_para,
74
interval)
75
76
return DiscreteCallback(stepsize_callback, stepsize_callback, # the first one is the condition, the second the affect!
77
save_positions = (false, false),
78
initialize = initialize!)
79
end
80
81
# Compatibility constructor used in `EulerAcousticsCouplingCallback`
82
function StepsizeCallback(cfl_hyperbolic)
83
RealT = typeof(cfl_hyperbolic)
84
return StepsizeCallback{RealT, RealT}(cfl_hyperbolic, zero(RealT), 1)
85
end
86
87
function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t,
88
integrator) where {Condition, Affect! <: StepsizeCallback}
89
return cb.affect!(integrator)
90
end
91
92
# this method is called to determine whether the callback should be activated
93
function (stepsize_callback::StepsizeCallback)(u, t, integrator)
94
@unpack interval = stepsize_callback
95
96
# Although the CFL-based timestep is usually not used with
97
# adaptive time integration methods, we still check the accepted steps `naccept` here.
98
return interval > 0 && integrator.stats.naccept % interval == 0
99
end
100
101
# This method is called as callback during the time integration.
102
@inline function (stepsize_callback::StepsizeCallback)(integrator)
103
if integrator.opts.adaptive
104
throw(ArgumentError("The `StepsizeCallback` has no effect when using an adaptive time integration scheme. Please remove the `StepsizeCallback` or set `adaptive = false` in `solve`."))
105
end
106
107
t = integrator.t
108
u_ode = integrator.u
109
semi = integrator.p
110
@unpack cfl_hyperbolic, cfl_parabolic = stepsize_callback
111
112
backend = trixi_backend(u_ode)
113
# Dispatch based on semidiscretization
114
dt = @trixi_timeit_ext backend timer() "calculate dt" calculate_dt(u_ode, t,
115
cfl_hyperbolic,
116
cfl_parabolic,
117
semi)
118
119
set_proposed_dt!(integrator, dt)
120
integrator.opts.dtmax = dt
121
integrator.dtcache = dt
122
123
# avoid re-evaluating possible FSAL stages
124
u_modified!(integrator, false)
125
return nothing
126
end
127
128
# Time integration methods from the DiffEq ecosystem without adaptive time stepping on their own
129
# such as `CarpenterKennedy2N54` require passing `dt=...` in `solve(ode, ...)`. Since we don't have
130
# an integrator at this stage but only the ODE, this method will be used there. It's called in
131
# many examples in `solve(ode, ..., dt=stepsize_callback(ode), ...)`.
132
function (cb::DiscreteCallback{Condition, Affect!})(ode::ODEProblem) where {Condition,
133
Affect! <:
134
StepsizeCallback
135
}
136
stepsize_callback = cb.affect!
137
@unpack cfl_hyperbolic, cfl_parabolic = stepsize_callback
138
u_ode = ode.u0
139
t = first(ode.tspan)
140
semi = ode.p
141
142
return calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic, semi)
143
end
144
145
# General case for an abstract single (i.e., non-coupled) semidiscretization
146
function calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic,
147
semi::AbstractSemidiscretization)
148
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
149
u = wrap_array(u_ode, mesh, equations, solver, cache)
150
151
return cfl_hyperbolic(t) * max_dt(u, t, mesh,
152
have_constant_speed(equations), equations,
153
solver, cache)
154
end
155
156
# Case for a purely parabolic semidiscretization
157
function calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic,
158
semi::SemidiscretizationParabolic)
159
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
160
u = wrap_array(u_ode, mesh, equations, solver, cache)
161
162
return cfl_parabolic(t) * max_dt(u, t, mesh,
163
have_constant_diffusivity(equations), equations,
164
equations, solver, cache)
165
end
166
167
# For Euler-Acoustic simulations with `EulerAcousticsCouplingCallback`
168
function calculate_dt(u_ode, t, cfl_hyperbolic::Real, cfl_parabolic::Real,
169
semi::AbstractSemidiscretization)
170
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
171
u = wrap_array(u_ode, mesh, equations, solver, cache)
172
173
return cfl_hyperbolic * max_dt(u, t, mesh,
174
have_constant_speed(equations), equations,
175
solver, cache)
176
end
177
178
# Case for a hyperbolic-parabolic semidiscretization
179
function calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic,
180
semi::SemidiscretizationHyperbolicParabolic)
181
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
182
equations_parabolic = semi.equations_parabolic
183
184
u = wrap_array(u_ode, mesh, equations, solver, cache)
185
186
dt_hyperbolic = cfl_hyperbolic(t) * max_dt(u, t, mesh,
187
have_constant_speed(equations), equations,
188
solver, cache)
189
190
cfl_para = cfl_parabolic(t)
191
if cfl_para > 0 # Check if parabolic CFL should be considered
192
dt_parabolic = cfl_para * max_dt(u, t, mesh,
193
have_constant_diffusivity(equations_parabolic), equations,
194
equations_parabolic, solver, cache)
195
196
return min(dt_hyperbolic, dt_parabolic)
197
else
198
return dt_hyperbolic
199
end
200
end
201
202
function calc_max_scaled_speed(backend::Nothing, u, mesh, constant_speed, equations, dg,
203
cache)
204
@unpack contravariant_vectors, inverse_jacobian = cache.elements
205
206
max_scaled_speed = zero(eltype(u))
207
@batch reduction=(max, max_scaled_speed) for element in eachelement(dg, cache)
208
max_lambda = max_scaled_speed_per_element(u, typeof(mesh), constant_speed,
209
equations, dg,
210
contravariant_vectors,
211
inverse_jacobian,
212
element)
213
# Use `Base.max` to prevent silent failures, as `max` from `@fastmath` doesn't propagate
214
# `NaN`s properly. See https://github.com/trixi-framework/Trixi.jl/pull/2445#discussion_r2336812323
215
max_scaled_speed = Base.max(max_scaled_speed, max_lambda)
216
end
217
return max_scaled_speed
218
end
219
220
function calc_max_scaled_speed(backend::Backend, u, ::MeshT, constant_speed, equations,
221
dg,
222
cache) where {MeshT}
223
@unpack contravariant_vectors, inverse_jacobian = cache.elements
224
225
num_elements = nelements(dg, cache)
226
init = neutral = AcceleratedKernels.neutral_element(Base.max, eltype(u))
227
228
# Provide a custom neutral and init element since we "reduce" over 1:num_elements
229
max_scaled_speed = AcceleratedKernels.mapreduce(Base.max, 1:num_elements, backend;
230
init, neutral) do element
231
max_scaled_speed_per_element(u, MeshT, constant_speed,
232
equations, dg,
233
contravariant_vectors,
234
inverse_jacobian,
235
element)
236
end
237
238
return max_scaled_speed
239
end
240
241
include("stepsize_dg1d.jl")
242
include("stepsize_dg2d.jl")
243
include("stepsize_dg3d.jl")
244
end # @muladd
245
246