Path: blob/main/src/time_integration/methods_SSP.jl
2055 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# Abstract base type for time integration schemes of explicit strong stability-preserving (SSP)8# Runge-Kutta (RK) methods. They are high-order time discretizations that guarantee the TVD property.9abstract type SimpleAlgorithmSSP <: AbstractTimeIntegrationAlgorithm end1011"""12SimpleSSPRK33(; stage_callbacks=())1314The third-order SSP Runge-Kutta method of Shu and Osher.1516## References1718- Shu, Osher (1988)19"Efficient Implementation of Essentially Non-oscillatory Shock-Capturing Schemes" (Eq. 2.18)20[DOI: 10.1016/0021-9991(88)90177-5](https://doi.org/10.1016/0021-9991(88)90177-5)21"""22struct SimpleSSPRK33{StageCallbacks} <: SimpleAlgorithmSSP23numerator_a::SVector{3, Float64}24numerator_b::SVector{3, Float64}25denominator::SVector{3, Float64}26c::SVector{3, Float64}27stage_callbacks::StageCallbacks2829function SimpleSSPRK33(; stage_callbacks = ())30# Mathematically speaking, it is not necessary for the algorithm to split the factors31# into numerator and denominator. Otherwise, however, rounding errors of the order of32# the machine accuracy will occur, which will add up over time and thus endanger the33# conservation of the simulation.34# See also https://github.com/trixi-framework/Trixi.jl/pull/1640.35numerator_a = SVector(0.0, 3.0, 1.0) # a = numerator_a / denominator36numerator_b = SVector(1.0, 1.0, 2.0) # b = numerator_b / denominator37denominator = SVector(1.0, 4.0, 3.0)38c = SVector(0.0, 1.0, 1 / 2)3940# Butcher tableau41# c | A42# 0 |43# 1 | 144# 1/2 | 1/4 1/445# --------------------46# b | 1/6 1/6 2/34748new{typeof(stage_callbacks)}(numerator_a, numerator_b, denominator, c,49stage_callbacks)50end51end5253# This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L154mutable struct SimpleIntegratorSSPOptions{Callback, TStops}55callback::Callback # callbacks; used in Trixi56adaptive::Bool # whether the algorithm is adaptive; ignored57dtmax::Float64 # ignored58maxiters::Int # maximal number of time steps59tstops::TStops # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored60end6162function SimpleIntegratorSSPOptions(callback, tspan; maxiters = typemax(Int), kwargs...)63tstops_internal = BinaryHeap{eltype(tspan)}(FasterForward())64# We add last(tspan) to make sure that the time integration stops at the end time65push!(tstops_internal, last(tspan))66# We add 2 * last(tspan) because add_tstop!(integrator, t) is only called by DiffEqCallbacks.jl if tstops contains a time that is larger than t67# (https://github.com/SciML/DiffEqCallbacks.jl/blob/025dfe99029bd0f30a2e027582744528eb92cd24/src/iterative_and_periodic.jl#L92)68push!(tstops_internal, 2 * last(tspan))69SimpleIntegratorSSPOptions{typeof(callback), typeof(tstops_internal)}(callback,70false, Inf,71maxiters,72tstops_internal)73end7475# This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L7776# This implements the interface components described at77# https://diffeq.sciml.ai/v6.8/basics/integrator/#Handing-Integrators-178# which are used in Trixi.79mutable struct SimpleIntegratorSSP{RealT <: Real, uType,80Params, Sol, F, Alg,81SimpleIntegratorSSPOptions} <: AbstractTimeIntegrator82u::uType83du::uType84u_tmp::uType85t::RealT86tdir::RealT # DIRection of time integration, i.e., if one marches forward or backward in time87dt::RealT # current time step88dtcache::RealT # manually set time step89iter::Int # current number of time steps (iteration)90p::Params # will be the semidiscretization from Trixi91sol::Sol # faked92f::F # `rhs!` of the semidiscretization93alg::Alg # SimpleSSPRK3394opts::SimpleIntegratorSSPOptions95finalstep::Bool # added for convenience96dtchangeable::Bool97force_stepfail::Bool98end99100"""101add_tstop!(integrator::SimpleIntegratorSSP, t)102Add a time stop during the time integration process.103This function is called after the periodic SaveSolutionCallback to specify the next stop to save the solution.104"""105function add_tstop!(integrator::SimpleIntegratorSSP, t)106integrator.tdir * (t - integrator.t) < zero(integrator.t) &&107error("Tried to add a tstop that is behind the current time. This is strictly forbidden")108# We need to remove the first entry of tstops when a new entry is added.109# Otherwise, the simulation gets stuck at the previous tstop and dt is adjusted to zero.110if length(integrator.opts.tstops) > 1111pop!(integrator.opts.tstops)112end113push!(integrator.opts.tstops, integrator.tdir * t)114end115116has_tstop(integrator::SimpleIntegratorSSP) = !isempty(integrator.opts.tstops)117first_tstop(integrator::SimpleIntegratorSSP) = first(integrator.opts.tstops)118119function init(ode::ODEProblem, alg::SimpleAlgorithmSSP;120dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs...)121u = copy(ode.u0)122du = similar(u)123u_tmp = similar(u)124t = first(ode.tspan)125tdir = sign(ode.tspan[end] - ode.tspan[1])126iter = 0127integrator = SimpleIntegratorSSP(u, du, u_tmp, t, tdir, dt, dt, iter, ode.p,128(prob = ode,), ode.f, alg,129SimpleIntegratorSSPOptions(callback, ode.tspan;130kwargs...),131false, true, false)132133# resize container134resize!(integrator.p, integrator.p.solver.volume_integral,135nelements(integrator.p.solver, integrator.p.cache))136137# Standard callbacks138initialize_callbacks!(callback, integrator)139140# Addition for `SimpleAlgorithmSSP` which may have stage callbacks141for stage_callback in alg.stage_callbacks142init_callback(stage_callback, integrator.p)143end144145return integrator146end147148function solve!(integrator::SimpleIntegratorSSP)149@unpack prob = integrator.sol150151integrator.finalstep = false152153@trixi_timeit timer() "main loop" while !integrator.finalstep154step!(integrator)155end156157# Empty the tstops array.158# This cannot be done in terminate!(integrator::SimpleIntegratorSSP) because DiffEqCallbacks.PeriodicCallbackAffect would return at error.159extract_all!(integrator.opts.tstops)160161for stage_callback in integrator.alg.stage_callbacks162finalize_callback(stage_callback, integrator.p)163end164165finalize_callbacks(integrator)166167return TimeIntegratorSolution((first(prob.tspan), integrator.t),168(prob.u0, integrator.u), prob)169end170171function step!(integrator::SimpleIntegratorSSP)172@unpack prob = integrator.sol173@unpack alg = integrator174t_end = last(prob.tspan)175callbacks = integrator.opts.callback176177@assert !integrator.finalstep178if isnan(integrator.dt)179error("time step size `dt` is NaN")180end181182modify_dt_for_tstops!(integrator)183184limit_dt!(integrator, t_end)185186@. integrator.u_tmp = integrator.u187for stage in eachindex(alg.c)188t_stage = integrator.t + integrator.dt * alg.c[stage]189# compute du190integrator.f(integrator.du, integrator.u, integrator.p, t_stage)191192# perform forward Euler step193@. integrator.u = integrator.u + integrator.dt * integrator.du194195for stage_callback in alg.stage_callbacks196stage_callback(integrator.u, integrator, stage)197end198199# perform convex combination200@. integrator.u = (alg.numerator_a[stage] * integrator.u_tmp +201alg.numerator_b[stage] * integrator.u) /202alg.denominator[stage]203end204integrator.iter += 1205integrator.t += integrator.dt206207@trixi_timeit timer() "Step-Callbacks" handle_callbacks!(callbacks, integrator)208209check_max_iter!(integrator)210211return nothing212end213214# get a cache where the RHS can be stored215get_tmp_cache(integrator::SimpleIntegratorSSP) = (integrator.u_tmp,)216217# some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u218u_modified!(integrator::SimpleIntegratorSSP, ::Bool) = false219220# stop the time integration221function terminate!(integrator::SimpleIntegratorSSP)222integrator.finalstep = true223224return nothing225end226227"""228modify_dt_for_tstops!(integrator::SimpleIntegratorSSP)229Modify the time-step size to match the time stops specified in integrator.opts.tstops.230To avoid adding OrdinaryDiffEq to Trixi's dependencies, this routine is a copy of231https://github.com/SciML/OrdinaryDiffEq.jl/blob/d76335281c540ee5a6d1bd8bb634713e004f62ee/src/integrators/integrator_utils.jl#L38-L54232"""233function modify_dt_for_tstops!(integrator::SimpleIntegratorSSP)234if has_tstop(integrator)235tdir_t = integrator.tdir * integrator.t236tdir_tstop = first_tstop(integrator)237if integrator.opts.adaptive238integrator.dt = integrator.tdir *239min(abs(integrator.dt), abs(tdir_tstop - tdir_t)) # step! to the end240elseif iszero(integrator.dtcache) && integrator.dtchangeable241integrator.dt = integrator.tdir * abs(tdir_tstop - tdir_t)242elseif integrator.dtchangeable && !integrator.force_stepfail243# always try to step! with dtcache, but lower if a tstop244# however, if force_stepfail then don't set to dtcache, and no tstop worry245integrator.dt = integrator.tdir *246min(abs(integrator.dtcache), abs(tdir_tstop - tdir_t)) # step! to the end247end248end249250return nothing251end252253# used for AMR254function Base.resize!(integrator::SimpleIntegratorSSP, new_size)255resize!(integrator.u, new_size)256resize!(integrator.du, new_size)257resize!(integrator.u_tmp, new_size)258259# Resize container260# new_size = n_variables * n_nodes^n_dims * n_elements261n_elements = nelements(integrator.p.solver, integrator.p.cache)262resize!(integrator.p, integrator.p.solver.volume_integral, n_elements)263264return nothing265end266end # @muladd267268269