diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index a40e187e82a..3d557915755 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.27.0 + uses: crate-ci/typos@v1.28.1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c636ee8b0b..020dd72d0fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,9 +133,9 @@ jobs: - uses: julia-actions/julia-processcoverage@v1 with: directories: src,examples,ext - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - file: ./lcov.info + files: ./lcov.info flags: unittests name: codecov-umbrella fail_ci_if_error: true diff --git a/examples/structured_1d_dgsem/elixir_burgers_perk3.jl b/examples/structured_1d_dgsem/elixir_burgers_perk3.jl index bf91fde74ea..5ff3aff3678 100644 --- a/examples/structured_1d_dgsem/elixir_burgers_perk3.jl +++ b/examples/structured_1d_dgsem/elixir_burgers_perk3.jl @@ -1,9 +1,9 @@ # Convex and ECOS are imported because they are used for finding the optimal time step and optimal -# monomial coefficients in the stability polynomial of P-ERK time integrators. +# monomial coefficients in the stability polynomial of PERK time integrators. using Convex, ECOS # NLsolve is imported to solve the system of nonlinear equations to find the coefficients -# in the Butcher tableau in the third order P-ERK time integrator. +# in the Butcher tableau in the third order PERK time integrator. using NLsolve using OrdinaryDiffEq diff --git a/examples/tree_1d_dgsem/elixir_advection_perk2.jl b/examples/tree_1d_dgsem/elixir_advection_perk2.jl index 69b587b42ec..86b173d6564 100644 --- a/examples/tree_1d_dgsem/elixir_advection_perk2.jl +++ b/examples/tree_1d_dgsem/elixir_advection_perk2.jl @@ -1,6 +1,6 @@ # Convex and ECOS are imported because they are used for finding the optimal time step and optimal -# monomial coefficients in the stability polynomial of P-ERK time integrators. +# monomial coefficients in the stability polynomial of PERK time integrators. using Convex, ECOS using OrdinaryDiffEq diff --git a/ext/TrixiConvexECOSExt.jl b/ext/TrixiConvexECOSExt.jl index 9b897436dbb..a83ac0a524f 100644 --- a/ext/TrixiConvexECOSExt.jl +++ b/ext/TrixiConvexECOSExt.jl @@ -1,7 +1,7 @@ # Package extension for adding Convex-based features to Trixi.jl module TrixiConvexECOSExt -# Required for coefficient optimization in P-ERK scheme integrators +# Required for coefficient optimization in PERK scheme integrators if isdefined(Base, :get_extension) using Convex: MOI, solve!, Variable, minimize, evaluate using ECOS: Optimizer diff --git a/ext/TrixiNLsolveExt.jl b/ext/TrixiNLsolveExt.jl index fa188d04c71..2e6bca0ba7b 100644 --- a/ext/TrixiNLsolveExt.jl +++ b/ext/TrixiNLsolveExt.jl @@ -2,7 +2,7 @@ module TrixiNLsolveExt # Required for finding coefficients in Butcher tableau in the third order of -# P-ERK scheme integrators +# PERK scheme integrators if isdefined(Base, :get_extension) using NLsolve: nlsolve else diff --git a/src/time_integration/paired_explicit_runge_kutta/methods_PERK2.jl b/src/time_integration/paired_explicit_runge_kutta/methods_PERK2.jl index 2451680a505..37d994dc9bf 100644 --- a/src/time_integration/paired_explicit_runge_kutta/methods_PERK2.jl +++ b/src/time_integration/paired_explicit_runge_kutta/methods_PERK2.jl @@ -34,8 +34,8 @@ function compute_PairedExplicitRK2_butcher_tableau(num_stages, eig_vals, tspan, # - 2 Since first entry of A is always zero (explicit method) and second is given by c_2 (consistency) coeffs_max = num_stages - 2 - a_matrix = zeros(coeffs_max, 2) - a_matrix[:, 1] = c[3:end] + a_matrix = zeros(2, coeffs_max) + a_matrix[1, :] = c[3:end] consistency_order = 2 @@ -56,8 +56,8 @@ function compute_PairedExplicitRK2_butcher_tableau(num_stages, eig_vals, tspan, num_monomial_coeffs = length(monomial_coeffs) @assert num_monomial_coeffs == coeffs_max A = compute_a_coeffs(num_stages, stage_scaling_factors, monomial_coeffs) - a_matrix[:, 1] -= A - a_matrix[:, 2] = A + a_matrix[1, :] -= A + a_matrix[2, :] = A end return a_matrix, c, dt_opt @@ -78,8 +78,8 @@ function compute_PairedExplicitRK2_butcher_tableau(num_stages, # - 2 Since first entry of A is always zero (explicit method) and second is given by c_2 (consistency) coeffs_max = num_stages - 2 - a_matrix = zeros(coeffs_max, 2) - a_matrix[:, 1] = c[3:end] + a_matrix = zeros(2, coeffs_max) + a_matrix[1, :] = c[3:end] path_monomial_coeffs = joinpath(base_path_monomial_coeffs, "gamma_" * string(num_stages) * ".txt") @@ -91,8 +91,8 @@ function compute_PairedExplicitRK2_butcher_tableau(num_stages, @assert num_monomial_coeffs == coeffs_max A = compute_a_coeffs(num_stages, stage_scaling_factors, monomial_coeffs) - a_matrix[:, 1] -= A - a_matrix[:, 2] = A + a_matrix[1, :] -= A + a_matrix[2, :] = A return a_matrix, c end @@ -131,16 +131,17 @@ optimized for a certain simulation setup (PDE, IC & BC, Riemann Solver, DG Solve Note: To use this integrator, the user must import the `Convex` and `ECOS` packages. """ -mutable struct PairedExplicitRK2 <: AbstractPairedExplicitRKSingle - const num_stages::Int +struct PairedExplicitRK2 <: AbstractPairedExplicitRKSingle + num_stages::Int a_matrix::Matrix{Float64} c::Vector{Float64} b1::Float64 bS::Float64 cS::Float64 + dt_opt::Union{Float64, Nothing} -end # struct PairedExplicitRK2 +end # Constructor that reads the coefficients from a file function PairedExplicitRK2(num_stages, base_path_monomial_coeffs::AbstractString, @@ -200,9 +201,8 @@ mutable struct PairedExplicitRK2Integrator{RealT <: Real, uType, Params, Sol, F, finalstep::Bool # added for convenience dtchangeable::Bool force_stepfail::Bool - # PairedExplicitRK2 stages: + # Additional PERK register k1::uType - k_higher::uType end function init(ode::ODEProblem, alg::PairedExplicitRK2; @@ -211,9 +211,7 @@ function init(ode::ODEProblem, alg::PairedExplicitRK2; du = zero(u0) u_tmp = zero(u0) - # PairedExplicitRK2 stages - k1 = zero(u0) - k_higher = zero(u0) + k1 = zero(u0) # Additional PERK register t0 = first(ode.tspan) tdir = sign(ode.tspan[end] - ode.tspan[1]) @@ -226,7 +224,7 @@ function init(ode::ODEProblem, alg::PairedExplicitRK2; ode.tspan; kwargs...), false, true, false, - k1, k_higher) + k1) # initialize callbacks if callback isa CallbackSet @@ -262,48 +260,21 @@ function step!(integrator::PairedExplicitRK2Integrator) end @trixi_timeit timer() "Paired Explicit Runge-Kutta ODE integration step" begin - # k1 - integrator.f(integrator.du, integrator.u, prob.p, integrator.t) - @threaded for i in eachindex(integrator.du) - integrator.k1[i] = integrator.du[i] * integrator.dt - end - - # Construct current state - @threaded for i in eachindex(integrator.u) - integrator.u_tmp[i] = integrator.u[i] + alg.c[2] * integrator.k1[i] - end - # k2 - integrator.f(integrator.du, integrator.u_tmp, prob.p, - integrator.t + alg.c[2] * integrator.dt) - - @threaded for i in eachindex(integrator.du) - integrator.k_higher[i] = integrator.du[i] * integrator.dt - end + # First and second stage are identical across all single/standalone PERK methods + PERK_k1!(integrator, prob.p) + PERK_k2!(integrator, prob.p, alg) # Higher stages for stage in 3:(alg.num_stages) - # Construct current state - @threaded for i in eachindex(integrator.u) - integrator.u_tmp[i] = integrator.u[i] + - alg.a_matrix[stage - 2, 1] * - integrator.k1[i] + - alg.a_matrix[stage - 2, 2] * - integrator.k_higher[i] - end - - integrator.f(integrator.du, integrator.u_tmp, prob.p, - integrator.t + alg.c[stage] * integrator.dt) - - @threaded for i in eachindex(integrator.du) - integrator.k_higher[i] = integrator.du[i] * integrator.dt - end + PERK_ki!(integrator, prob.p, alg, stage) end @threaded for i in eachindex(integrator.u) - integrator.u[i] += alg.b1 * integrator.k1[i] + - alg.bS * integrator.k_higher[i] + integrator.u[i] += integrator.dt * + (alg.b1 * integrator.k1[i] + + alg.bS * integrator.du[i]) end - end # PairedExplicitRK2 step + end integrator.iter += 1 integrator.t += integrator.dt diff --git a/src/time_integration/paired_explicit_runge_kutta/methods_PERK3.jl b/src/time_integration/paired_explicit_runge_kutta/methods_PERK3.jl index 02bc3eeba36..5be1d498d59 100644 --- a/src/time_integration/paired_explicit_runge_kutta/methods_PERK3.jl +++ b/src/time_integration/paired_explicit_runge_kutta/methods_PERK3.jl @@ -59,11 +59,11 @@ function compute_PairedExplicitRK3_butcher_tableau(num_stages, tspan, monomial_coeffs, c; verbose) end - # Fill A-matrix in P-ERK style - a_matrix = zeros(num_stages - 2, 2) - a_matrix[:, 1] = c[3:end] - a_matrix[:, 1] -= a_unknown - a_matrix[:, 2] = a_unknown + # Fill A-matrix in PERK style + a_matrix = zeros(2, num_stages - 2) + a_matrix[1, :] = c[3:end] + a_matrix[1, :] -= a_unknown + a_matrix[2, :] = a_unknown return a_matrix, c, dt_opt end @@ -80,8 +80,8 @@ function compute_PairedExplicitRK3_butcher_tableau(num_stages, # - 2 Since First entry of A is always zero (explicit method) and second is given by c_2 (consistency) a_coeffs_max = num_stages - 2 - a_matrix = zeros(a_coeffs_max, 2) - a_matrix[:, 1] = c[3:end] + a_matrix = zeros(2, a_coeffs_max) + a_matrix[1, :] = c[3:end] path_a_coeffs = joinpath(base_path_a_coeffs, "a_" * string(num_stages) * ".txt") @@ -91,9 +91,9 @@ function compute_PairedExplicitRK3_butcher_tableau(num_stages, num_a_coeffs = size(a_coeffs, 1) @assert num_a_coeffs == a_coeffs_max - # Fill A-matrix in P-ERK style - a_matrix[:, 1] -= a_coeffs - a_matrix[:, 2] = a_coeffs + # Fill A-matrix in PERK style + a_matrix[1, :] -= a_coeffs + a_matrix[2, :] = a_coeffs return a_matrix, c end @@ -107,7 +107,7 @@ end verbose = false, cS2 = 1.0f0) Parameters: - - `num_stages` (`Int`): Number of stages in the paired explicit Runge-Kutta (P-ERK) method. + - `num_stages` (`Int`): Number of stages in the paired explicit Runge-Kutta (PERK) method. - `base_path_a_coeffs` (`AbstractString`): Path to a file containing some coefficients in the A-matrix in the Butcher tableau of the Runge Kutta method. The matrix should be stored in a text file at `joinpath(base_path_a_coeffs, "a_$(num_stages).txt")` and separated by line breaks. @@ -122,7 +122,7 @@ end s is the number of stages, default is 1.0f0. The following structures and methods provide an implementation of -the third-order paired explicit Runge-Kutta (P-ERK) method +the third-order paired explicit Runge-Kutta (PERK) method optimized for a certain simulation setup (PDE, IC & BC, Riemann Solver, DG Solver). The original paper is - Nasab, Vermeire (2022) @@ -135,13 +135,14 @@ Multirate Time-Integration based on Dynamic ODE Partitioning through Adaptively Note: To use this integrator, the user must import the `Convex`, `ECOS`, and `NLsolve` packages. """ -mutable struct PairedExplicitRK3 <: AbstractPairedExplicitRKSingle - const num_stages::Int # S +struct PairedExplicitRK3 <: AbstractPairedExplicitRKSingle + num_stages::Int # S a_matrix::Matrix{Float64} c::Vector{Float64} + dt_opt::Union{Float64, Nothing} -end # struct PairedExplicitRK3 +end # Constructor for previously computed A Coeffs function PairedExplicitRK3(num_stages, base_path_a_coeffs::AbstractString, @@ -195,9 +196,9 @@ mutable struct PairedExplicitRK3Integrator{RealT <: Real, uType, Params, Sol, F, finalstep::Bool # added for convenience dtchangeable::Bool force_stepfail::Bool - # PairedExplicitRK stages: + # Additional PERK3 registers k1::uType - k_higher::uType + kS1::uType end function init(ode::ODEProblem, alg::PairedExplicitRK3; @@ -206,9 +207,9 @@ function init(ode::ODEProblem, alg::PairedExplicitRK3; du = zero(u0) u_tmp = zero(u0) - # PairedExplicitRK stages + # Additional PERK3 registers k1 = zero(u0) - k_higher = zero(u0) + kS1 = zero(u0) t0 = first(ode.tspan) tdir = sign(ode.tspan[end] - ode.tspan[1]) @@ -221,7 +222,7 @@ function init(ode::ODEProblem, alg::PairedExplicitRK3; ode.tspan; kwargs...), false, true, false, - k1, k_higher) + k1, kS1) # initialize callbacks if callback isa CallbackSet @@ -258,72 +259,42 @@ function step!(integrator::PairedExplicitRK3Integrator) end @trixi_timeit timer() "Paired Explicit Runge-Kutta ODE integration step" begin - # k1 - integrator.f(integrator.du, integrator.u, prob.p, integrator.t) - @threaded for i in eachindex(integrator.du) - integrator.k1[i] = integrator.du[i] * integrator.dt - end - - # Construct current state - @threaded for i in eachindex(integrator.du) - integrator.u_tmp[i] = integrator.u[i] + alg.c[2] * integrator.k1[i] - end - # k2 - integrator.f(integrator.du, integrator.u_tmp, prob.p, - integrator.t + alg.c[2] * integrator.dt) - - @threaded for i in eachindex(integrator.du) - integrator.k_higher[i] = integrator.du[i] * integrator.dt - end + # First and second stage are identical across all single/standalone PERK methods + PERK_k1!(integrator, prob.p) + PERK_k2!(integrator, prob.p, alg) - # Higher stages for stage in 3:(alg.num_stages - 1) - # Construct current state - @threaded for i in eachindex(integrator.du) - integrator.u_tmp[i] = integrator.u[i] + - alg.a_matrix[stage - 2, 1] * - integrator.k1[i] + - alg.a_matrix[stage - 2, 2] * - integrator.k_higher[i] - end - - integrator.f(integrator.du, integrator.u_tmp, prob.p, - integrator.t + alg.c[stage] * integrator.dt) - - @threaded for i in eachindex(integrator.du) - integrator.k_higher[i] = integrator.du[i] * integrator.dt - end + PERK_ki!(integrator, prob.p, alg, stage) end - # Last stage - @threaded for i in eachindex(integrator.du) - integrator.u_tmp[i] = integrator.u[i] + - alg.a_matrix[alg.num_stages - 2, 1] * - integrator.k1[i] + - alg.a_matrix[alg.num_stages - 2, 2] * - integrator.k_higher[i] + # We need to store `du` of the S-1 stage in `kS1` for the final update: + @threaded for i in eachindex(integrator.u) + integrator.kS1[i] = integrator.du[i] end - integrator.f(integrator.du, integrator.u_tmp, prob.p, - integrator.t + alg.c[alg.num_stages] * integrator.dt) + PERK_ki!(integrator, prob.p, alg, alg.num_stages) @threaded for i in eachindex(integrator.u) # "Own" PairedExplicitRK based on SSPRK33. - # Note that 'k_higher' carries the values of K_{S-1} + # Note that 'kS1' carries the values of K_{S-1} # and that we construct 'K_S' "in-place" from 'integrator.du' - integrator.u[i] += (integrator.k1[i] + integrator.k_higher[i] + - 4.0 * integrator.du[i] * integrator.dt) / 6.0 + integrator.u[i] += integrator.dt * + (integrator.k1[i] + integrator.kS1[i] + + 4.0 * integrator.du[i]) / 6.0 end - end # PairedExplicitRK step timer + end integrator.iter += 1 integrator.t += integrator.dt - # handle callbacks - if callbacks isa CallbackSet - for cb in callbacks.discrete_callbacks - if cb.condition(integrator.u, integrator.t, integrator) - cb.affect!(integrator) + @trixi_timeit timer() "Step-Callbacks" begin + # handle callbacks + if callbacks isa CallbackSet + foreach(callbacks.discrete_callbacks) do cb + if cb.condition(integrator.u, integrator.t, integrator) + cb.affect!(integrator) + end + return nothing end end end @@ -334,4 +305,13 @@ function step!(integrator::PairedExplicitRK3Integrator) terminate!(integrator) end end + +function Base.resize!(integrator::PairedExplicitRK3Integrator, new_size) + resize!(integrator.u, new_size) + resize!(integrator.du, new_size) + resize!(integrator.u_tmp, new_size) + + resize!(integrator.k1, new_size) + resize!(integrator.kS1, new_size) +end end # @muladd diff --git a/src/time_integration/paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl b/src/time_integration/paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl index c606326738f..87f067fc39d 100644 --- a/src/time_integration/paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl +++ b/src/time_integration/paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl @@ -108,13 +108,42 @@ function solve!(integrator::AbstractPairedExplicitRKIntegrator) @trixi_timeit timer() "main loop" while !integrator.finalstep step!(integrator) - end # "main loop" timer + end return TimeIntegratorSolution((first(prob.tspan), integrator.t), (prob.u0, integrator.u), integrator.sol.prob) end +# Function that computes the first stage of a general PERK method +@inline function PERK_k1!(integrator::AbstractPairedExplicitRKIntegrator, p) + integrator.f(integrator.k1, integrator.u, p, integrator.t) +end + +@inline function PERK_k2!(integrator::AbstractPairedExplicitRKSingleIntegrator, p, alg) + @threaded for i in eachindex(integrator.du) + integrator.u_tmp[i] = integrator.u[i] + + alg.c[2] * integrator.dt * integrator.k1[i] + end + + integrator.f(integrator.du, integrator.u_tmp, p, + integrator.t + alg.c[2] * integrator.dt) +end + +@inline function PERK_ki!(integrator::AbstractPairedExplicitRKSingleIntegrator, p, alg, + stage) + # Construct current state + @threaded for i in eachindex(integrator.u) + integrator.u_tmp[i] = integrator.u[i] + + integrator.dt * + (alg.a_matrix[1, stage - 2] * integrator.k1[i] + + alg.a_matrix[2, stage - 2] * integrator.du[i]) + end + + integrator.f(integrator.du, integrator.u_tmp, p, + integrator.t + alg.c[stage] * integrator.dt) +end + # used for AMR (Adaptive Mesh Refinement) function Base.resize!(integrator::AbstractPairedExplicitRKIntegrator, new_size) resize!(integrator.u, new_size) @@ -122,7 +151,6 @@ function Base.resize!(integrator::AbstractPairedExplicitRKIntegrator, new_size) resize!(integrator.u_tmp, new_size) resize!(integrator.k1, new_size) - resize!(integrator.k_higher, new_size) end # get a cache where the RHS can be stored diff --git a/test/test_structured_1d.jl b/test/test_structured_1d.jl index 9910dd5b5cc..a5b561ed8fe 100644 --- a/test/test_structured_1d.jl +++ b/test/test_structured_1d.jl @@ -86,6 +86,15 @@ end du_ode = similar(u_ode) @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 8000 end + + # Test `resize!` + integrator = init(ode, ode_algorithm, dt = 42.0, callback = callbacks) + resize!(integrator, 42) + @test length(integrator.u) == 42 + @test length(integrator.du) == 42 + @test length(integrator.u_tmp) == 42 + @test length(integrator.k1) == 42 + @test length(integrator.kS1) == 42 end # Testing the third-order paired explicit Runge-Kutta (PERK) method without stepsize callback @@ -96,8 +105,8 @@ end save_solution=SaveSolutionCallback(dt = 0.1 + 1.0e-8), # Adding a small epsilon to avoid floating-point precision issues callbacks=CallbackSet(summary_callback, save_solution, analysis_callback, alive_callback), - l2=[5.726144786001842e-7], - linf=[3.430730019182704e-6]) + l2=[5.726144824784944e-7], + linf=[3.43073006914274e-6]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let diff --git a/test/test_unit.jl b/test/test_unit.jl index 4b4742fdc43..97e32726ca7 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -1706,7 +1706,7 @@ end ode_algorithm = Trixi.PairedExplicitRK2(6, path_coeff_file) - @test isapprox(ode_algorithm.a_matrix, + @test isapprox(transpose(ode_algorithm.a_matrix), [0.12405417889682908 0.07594582110317093 0.16178873711001726 0.13821126288998273 0.16692313960864164 0.2330768603913584 @@ -1719,7 +1719,7 @@ end tspan = (0.0, 1.0) ode_algorithm = Trixi.PairedExplicitRK2(12, tspan, vec(eig_vals)) - @test isapprox(ode_algorithm.a_matrix, + @test isapprox(transpose(ode_algorithm.a_matrix), [0.06453812656711647 0.02637096434197444 0.09470601372274887 0.041657622640887494 0.12332877820069793 0.058489403617483886 @@ -1739,7 +1739,7 @@ end ode_algorithm = Trixi.PairedExplicitRK3(8, path_coeff_file) - @test isapprox(ode_algorithm.a_matrix, + @test isapprox(transpose(ode_algorithm.a_matrix), [0.33551678438002486 0.06448322158043965 0.49653494442225443 0.10346507941960345 0.6496890912144586 0.15031092070647037 @@ -1754,7 +1754,7 @@ end tspan = (0.0, 1.0) ode_algorithm = Trixi.PairedExplicitRK3(13, tspan, vec(eig_vals)) - @test isapprox(ode_algorithm.a_matrix, + @test isapprox(transpose(ode_algorithm.a_matrix), [0.19121164778938382 0.008788355190848427 0.28723462747227385 0.012765384448655121 0.38017717196008227 0.019822834000382223