Path: blob/main/src/callbacks_step/stepsize.jl
5586 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67"""8StepsizeCallback(; cfl=1.0, cfl_parabolic = 0.0,9interval = 1)1011Set the time step size according to a CFL condition with hyperbolic CFL number `cfl`12if the time integration method isn't adaptive itself.13The hyperbolic CFL number `cfl` must be either a `Real` number, corresponding to a constant14CFL number, or a function of time `t` returning a `Real` number.15The latter approach allows for variable CFL numbers that can be used to realize, e.g.,16a ramp-up of the time step.1718One can additionally supply a parabolic CFL number `cfl_parabolic` to19limit the admissible timestep also respecting parabolic restrictions.20This is only applicable for semidiscretizations of type21[`SemidiscretizationHyperbolicParabolic`](@ref) and [`SemidiscretizationParabolic`](@ref).22To enable checking for parabolic timestep restrictions, provide a value greater than zero for `cfl_parabolic`.23By default, `cfl_parabolic` is set to zero which means that only the hyperbolic CFL number `cfl` is considered.24The keyword argument `cfl_parabolic` must be either a `Real` number, corresponding to a constant25parabolic CFL number, or a function of time `t` returning a `Real` number.2627By default, the timestep will be adjusted at every step.28For different values of `interval`, the timestep will be adjusted every `interval` steps.29"""30struct StepsizeCallback{CflHyperbolicType, CflParabolicType}31cfl_hyperbolic::CflHyperbolicType32cfl_parabolic::CflParabolicType33interval::Int34end3536function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:StepsizeCallback})37@nospecialize cb # reduce precompilation time3839stepsize_callback = cb.affect!40@unpack cfl_hyperbolic, cfl_parabolic, interval = stepsize_callback41print(io, "StepsizeCallback(",42"cfl_hyperbolic=", cfl_hyperbolic, ", ",43"cfl_parabolic=", cfl_parabolic, ", ",44"interval=", interval, ")")45return nothing46end4748function Base.show(io::IO, ::MIME"text/plain",49cb::DiscreteCallback{<:Any, <:StepsizeCallback})50@nospecialize cb # reduce precompilation time5152if get(io, :compact, false)53show(io, cb)54else55stepsize_callback = cb.affect!5657setup = [58"CFL Hyperbolic" => stepsize_callback.cfl_hyperbolic,59"CFL Parabolic" => stepsize_callback.cfl_parabolic,60"Interval" => stepsize_callback.interval61]62summary_box(io, "StepsizeCallback", setup)63end64end6566function StepsizeCallback(; cfl = 1.0, cfl_parabolic = 0.0,67interval = 1)68# Convert plain real numbers to functions for unified treatment69cfl_hyp = isa(cfl, Real) ? Returns(cfl) : cfl70cfl_para = isa(cfl_parabolic, Real) ? Returns(cfl_parabolic) : cfl_parabolic71stepsize_callback = StepsizeCallback{typeof(cfl_hyp), typeof(cfl_para)}(cfl_hyp,72cfl_para,73interval)7475return DiscreteCallback(stepsize_callback, stepsize_callback, # the first one is the condition, the second the affect!76save_positions = (false, false),77initialize = initialize!)78end7980# Compatibility constructor used in `EulerAcousticsCouplingCallback`81function StepsizeCallback(cfl_hyperbolic)82RealT = typeof(cfl_hyperbolic)83return StepsizeCallback{RealT, RealT}(cfl_hyperbolic, zero(RealT), 1)84end8586function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t,87integrator) where {Condition, Affect! <: StepsizeCallback}88return cb.affect!(integrator)89end9091# this method is called to determine whether the callback should be activated92function (stepsize_callback::StepsizeCallback)(u, t, integrator)93@unpack interval = stepsize_callback9495# Although the CFL-based timestep is usually not used with96# adaptive time integration methods, we still check the accepted steps `naccept` here.97return interval > 0 && integrator.stats.naccept % interval == 098end99100# This method is called as callback during the time integration.101@inline function (stepsize_callback::StepsizeCallback)(integrator)102if integrator.opts.adaptive103throw(ArgumentError("The `StepsizeCallback` has no effect when using an adaptive time integration scheme. Please remove the `StepsizeCallback` or set `adaptive = false` in `solve`."))104end105106t = integrator.t107u_ode = integrator.u108semi = integrator.p109@unpack cfl_hyperbolic, cfl_parabolic = stepsize_callback110111backend = trixi_backend(u_ode)112# Dispatch based on semidiscretization113dt = @trixi_timeit_ext backend timer() "calculate dt" calculate_dt(u_ode, t,114cfl_hyperbolic,115cfl_parabolic,116semi)117118set_proposed_dt!(integrator, dt)119integrator.opts.dtmax = dt120integrator.dtcache = dt121122# avoid re-evaluating possible FSAL stages123u_modified!(integrator, false)124return nothing125end126127# Time integration methods from the DiffEq ecosystem without adaptive time stepping on their own128# such as `CarpenterKennedy2N54` require passing `dt=...` in `solve(ode, ...)`. Since we don't have129# an integrator at this stage but only the ODE, this method will be used there. It's called in130# many examples in `solve(ode, ..., dt=stepsize_callback(ode), ...)`.131function (cb::DiscreteCallback{Condition, Affect!})(ode::ODEProblem) where {Condition,132Affect! <:133StepsizeCallback134}135stepsize_callback = cb.affect!136@unpack cfl_hyperbolic, cfl_parabolic = stepsize_callback137u_ode = ode.u0138t = first(ode.tspan)139semi = ode.p140141return calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic, semi)142end143144# General case for an abstract single (i.e., non-coupled) semidiscretization145function calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic,146semi::AbstractSemidiscretization)147mesh, equations, solver, cache = mesh_equations_solver_cache(semi)148u = wrap_array(u_ode, mesh, equations, solver, cache)149150return cfl_hyperbolic(t) * max_dt(u, t, mesh,151have_constant_speed(equations), equations,152solver, cache)153end154155# Case for a purely parabolic semidiscretization156function calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic,157semi::SemidiscretizationParabolic)158mesh, equations, solver, cache = mesh_equations_solver_cache(semi)159u = wrap_array(u_ode, mesh, equations, solver, cache)160161return cfl_parabolic(t) * max_dt(u, t, mesh,162have_constant_diffusivity(equations), equations,163equations, solver, cache)164end165166# For Euler-Acoustic simulations with `EulerAcousticsCouplingCallback`167function calculate_dt(u_ode, t, cfl_hyperbolic::Real, cfl_parabolic::Real,168semi::AbstractSemidiscretization)169mesh, equations, solver, cache = mesh_equations_solver_cache(semi)170u = wrap_array(u_ode, mesh, equations, solver, cache)171172return cfl_hyperbolic * max_dt(u, t, mesh,173have_constant_speed(equations), equations,174solver, cache)175end176177# Case for a hyperbolic-parabolic semidiscretization178function calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic,179semi::SemidiscretizationHyperbolicParabolic)180mesh, equations, solver, cache = mesh_equations_solver_cache(semi)181equations_parabolic = semi.equations_parabolic182183u = wrap_array(u_ode, mesh, equations, solver, cache)184185dt_hyperbolic = cfl_hyperbolic(t) * max_dt(u, t, mesh,186have_constant_speed(equations), equations,187solver, cache)188189cfl_para = cfl_parabolic(t)190if cfl_para > 0 # Check if parabolic CFL should be considered191dt_parabolic = cfl_para * max_dt(u, t, mesh,192have_constant_diffusivity(equations_parabolic), equations,193equations_parabolic, solver, cache)194195return min(dt_hyperbolic, dt_parabolic)196else197return dt_hyperbolic198end199end200201function calc_max_scaled_speed(backend::Nothing, u, mesh, constant_speed, equations, dg,202cache)203@unpack contravariant_vectors, inverse_jacobian = cache.elements204205max_scaled_speed = zero(eltype(u))206@batch reduction=(max, max_scaled_speed) for element in eachelement(dg, cache)207max_lambda = max_scaled_speed_per_element(u, typeof(mesh), constant_speed,208equations, dg,209contravariant_vectors,210inverse_jacobian,211element)212# Use `Base.max` to prevent silent failures, as `max` from `@fastmath` doesn't propagate213# `NaN`s properly. See https://github.com/trixi-framework/Trixi.jl/pull/2445#discussion_r2336812323214max_scaled_speed = Base.max(max_scaled_speed, max_lambda)215end216return max_scaled_speed217end218219function calc_max_scaled_speed(backend::Backend, u, ::MeshT, constant_speed, equations,220dg,221cache) where {MeshT}222@unpack contravariant_vectors, inverse_jacobian = cache.elements223224num_elements = nelements(dg, cache)225init = neutral = AcceleratedKernels.neutral_element(Base.max, eltype(u))226227# Provide a custom neutral and init element since we "reduce" over 1:num_elements228max_scaled_speed = AcceleratedKernels.mapreduce(Base.max, 1:num_elements, backend;229init, neutral) do element230max_scaled_speed_per_element(u, MeshT, constant_speed,231equations, dg,232contravariant_vectors,233inverse_jacobian,234element)235end236237return max_scaled_speed238end239240include("stepsize_dg1d.jl")241include("stepsize_dg2d.jl")242include("stepsize_dg3d.jl")243end # @muladd244245246