Skip to content

Commit e35bb19

Browse files
Time-dependent CFL (#2248)
* Variable CFL * Example * fix text * fix * CFL for coupled semi * nnews * Apply suggestions from code review Co-authored-by: Hendrik Ranocha <[email protected]> * Update src/callbacks_step/stepsize.jl Co-authored-by: Hendrik Ranocha <[email protected]> * shorten code * coupled glm * fmt * Update test/test_structured_2d.jl --------- Co-authored-by: Hendrik Ranocha <[email protected]>
1 parent 900322e commit e35bb19

File tree

7 files changed

+268
-27
lines changed

7 files changed

+268
-27
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ for human readability.
1414
- `LobattoLegendreBasis` and related datastructures made fully floating-type general,
1515
enabling calculations with higher than double (`Float64`) precision ([#2128])
1616
- In 2D, quadratic elements, i.e., 8-node (quadratic) quadrilaterals are now supported in standard Abaqus `inp` format ([#2217])
17+
- The `cfl` value supplied in the `StepsizeCallback` and `GlmStepsizeCallback` can now be a function of simulation
18+
time `t` to enable e.g. a ramp-up of the CFL value.
19+
This is useful for simulations that are initialized with an "unphysical" initial condition, but do not permit the usage of
20+
adaptive, error-based timestepping.
21+
Examples for this are simulations involving the MHD equations which require in general the `GlmStepsizeCallback` ([#2248])
1722

1823
#### Changed
1924

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
using OrdinaryDiffEq
2+
using Trixi
3+
4+
###############################################################################
5+
# semidiscretization of the compressible ideal GLM-MHD equations
6+
equations = IdealGlmMhdEquations2D(1.4)
7+
8+
"""
9+
initial_condition_rotor(x, t, equations::IdealGlmMhdEquations2D)
10+
11+
The classical MHD rotor test case. Here, the setup is taken from
12+
- Dominik Derigs, Gregor J. Gassner, Stefanie Walch & Andrew R. Winters (2018)
13+
Entropy Stable Finite Volume Approximations for Ideal Magnetohydrodynamics
14+
[doi: 10.1365/s13291-018-0178-9](https://doi.org/10.1365/s13291-018-0178-9)
15+
"""
16+
function initial_condition_rotor(x, t, equations::IdealGlmMhdEquations2D)
17+
# setup taken from Derigs et al. DMV article (2018)
18+
# domain must be [0, 1] x [0, 1], γ = 1.4
19+
dx = x[1] - 0.5
20+
dy = x[2] - 0.5
21+
r = sqrt(dx^2 + dy^2)
22+
f = (0.115 - r) / 0.015
23+
if r <= 0.1
24+
rho = 10.0
25+
v1 = -20.0 * dy
26+
v2 = 20.0 * dx
27+
elseif r >= 0.115
28+
rho = 1.0
29+
v1 = 0.0
30+
v2 = 0.0
31+
else
32+
rho = 1.0 + 9.0 * f
33+
v1 = -20.0 * f * dy
34+
v2 = 20.0 * f * dx
35+
end
36+
v3 = 0.0
37+
p = 1.0
38+
B1 = 5.0 / sqrt(4.0 * pi)
39+
B2 = 0.0
40+
B3 = 0.0
41+
psi = 0.0
42+
return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, psi), equations)
43+
end
44+
initial_condition = initial_condition_rotor
45+
46+
surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell)
47+
volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell)
48+
polydeg = 4
49+
basis = LobattoLegendreBasis(polydeg)
50+
indicator_sc = IndicatorHennemannGassner(equations, basis,
51+
alpha_max = 0.5,
52+
alpha_min = 0.001,
53+
alpha_smooth = true,
54+
variable = density_pressure)
55+
volume_integral = VolumeIntegralShockCapturingHG(indicator_sc;
56+
volume_flux_dg = volume_flux,
57+
volume_flux_fv = surface_flux)
58+
solver = DGSEM(basis, surface_flux, volume_integral)
59+
60+
# Affine type mapping to take the [-1,1]^2 domain from the mesh file
61+
# and put it onto the rotor domain [0,1]^2 and then warp it with a mapping
62+
# as described in https://arxiv.org/abs/2012.12040
63+
function mapping_twist(xi, eta)
64+
y = 0.5 * (eta + 1.0) +
65+
0.05 * cos(1.5 * pi * (2.0 * xi - 1.0)) * cos(0.5 * pi * (2.0 * eta - 1.0))
66+
x = 0.5 * (xi + 1.0) + 0.05 * cos(0.5 * pi * (2.0 * xi - 1.0)) * cos(2.0 * pi * y)
67+
return SVector(x, y)
68+
end
69+
70+
mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp",
71+
joinpath(@__DIR__, "square_unstructured_2.inp"))
72+
73+
mesh = P4estMesh{2}(mesh_file,
74+
polydeg = 4,
75+
mapping = mapping_twist,
76+
initial_refinement_level = 1)
77+
78+
boundary_condition = BoundaryConditionDirichlet(initial_condition)
79+
boundary_conditions = Dict(:all => boundary_condition)
80+
81+
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
82+
boundary_conditions = boundary_conditions)
83+
84+
###############################################################################
85+
# ODE solvers, callbacks etc.
86+
87+
tspan = (0.0, 0.15)
88+
ode = semidiscretize(semi, tspan)
89+
90+
summary_callback = SummaryCallback()
91+
92+
analysis_interval = 100
93+
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
94+
95+
alive_callback = AliveCallback(analysis_interval = analysis_interval)
96+
97+
save_solution = SaveSolutionCallback(interval = 100,
98+
save_initial_solution = true,
99+
save_final_solution = true,
100+
solution_variables = cons2prim)
101+
102+
amr_indicator = IndicatorLöhner(semi,
103+
variable = density_pressure)
104+
105+
amr_controller = ControllerThreeLevel(semi, amr_indicator,
106+
base_level = 1,
107+
med_level = 3, med_threshold = 0.05,
108+
max_level = 5, max_threshold = 0.1)
109+
amr_callback = AMRCallback(semi, amr_controller,
110+
interval = 3,
111+
adapt_initial_condition = true,
112+
adapt_initial_condition_only_refine = true)
113+
114+
# increase the CFL number linearly from cfl_0() at time 0
115+
# to cfl_t_ramp() at time t = t_ramp(), keep it constant afterward
116+
cfl_0() = 0.5
117+
cfl_t_ramp() = 1.2
118+
t_ramp() = 0.1
119+
cfl(t) = min(cfl_0() + (cfl_t_ramp() - cfl_0()) / t_ramp() * t, cfl_t_ramp())
120+
121+
stepsize_callback = StepsizeCallback(cfl = cfl)
122+
123+
glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl)
124+
125+
callbacks = CallbackSet(summary_callback,
126+
analysis_callback,
127+
alive_callback,
128+
save_solution,
129+
amr_callback,
130+
stepsize_callback,
131+
glm_speed_callback)
132+
133+
###############################################################################
134+
# run the simulation
135+
136+
sol = solve(ode,
137+
CarpenterKennedy2N54(thread = OrdinaryDiffEq.True(),
138+
williamson_condition = false),
139+
dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
140+
save_everystep = false, callback = callbacks);
141+
summary_callback() # print the timer summary

src/callbacks_step/glm_speed.jl

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
Update the divergence cleaning wave speed `c_h` according to the time step
1212
computed in [`StepsizeCallback`](@ref) for the ideal GLM-MHD equations, the multi-component
1313
GLM-MHD equations, and the multi-ion GLM-MHD equations.
14-
The `cfl` number should be set to the same value as for the time step size calculation. The
15-
`glm_scale` ensures that the GLM wave speed is lower than the fastest physical waves in the MHD
14+
The `cfl` number should be set to the same value as for the time step size calculation.
15+
As for standard [`StepsizeCallback`](@ref) `cfl` can be either set to a `Real` number or
16+
a function of time `t` returning a `Real` number.
17+
18+
The `glm_scale` ensures that the GLM wave speed is lower than the fastest physical waves in the MHD
1619
solution and should thus be set to a value within the interval [0,1]. Note that `glm_scale = 0`
1720
deactivates the divergence cleaning.
1821
@@ -22,9 +25,9 @@ semidiscretization, the divergence cleaning should be applied. See also
2225
Note: `SemidiscretizationCoupled` and all related features are considered experimental and
2326
may change at any time.
2427
"""
25-
struct GlmSpeedCallback{RealT <: Real}
28+
struct GlmSpeedCallback{RealT <: Real, CflType}
2629
glm_scale::RealT
27-
cfl::RealT
30+
cfl::CflType
2831
semi_indices::Vector{Int}
2932
end
3033

@@ -58,7 +61,9 @@ end
5861
function GlmSpeedCallback(; glm_scale = 0.5, cfl, semi_indices = Int[])
5962
@assert 0<=glm_scale<=1 "glm_scale must be between 0 and 1"
6063

61-
glm_speed_callback = GlmSpeedCallback(glm_scale, cfl, semi_indices)
64+
glm_speed_callback = GlmSpeedCallback{typeof(glm_scale), typeof(cfl)}(glm_scale,
65+
cfl,
66+
semi_indices)
6267

6368
DiscreteCallback(glm_speed_callback, glm_speed_callback, # the first one is the condition, the second the affect!
6469
save_positions = (false, false),
@@ -75,13 +80,17 @@ function (glm_speed_callback::GlmSpeedCallback)(u, t, integrator)
7580
return true
7681
end
7782

78-
function update_cleaning_speed!(semi, glm_speed_callback, dt)
83+
function update_cleaning_speed!(semi, glm_speed_callback, dt, t)
7984
@unpack glm_scale, cfl = glm_speed_callback
8085

8186
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
8287

8388
# compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR)
84-
c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache)
89+
if cfl isa Real # Case for constant CFL
90+
c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache)
91+
else # Variable CFL
92+
c_h_deltat = calc_dt_for_cleaning_speed(cfl(t), mesh, equations, solver, cache)
93+
end
8594

8695
# c_h is proportional to its own time step divided by the complete MHD time step
8796
# We use @reset here since the equations are immutable (to work on GPUs etc.).
@@ -99,7 +108,7 @@ end
99108

100109
# Call the appropriate update function (this indirection allows to specialize on,
101110
# e.g., the semidiscretization type)
102-
update_cleaning_speed!(semi, glm_speed_callback, dt)
111+
update_cleaning_speed!(semi, glm_speed_callback, dt, integrator.t)
103112

104113
# avoid re-evaluating possible FSAL stages
105114
u_modified!(integrator, false)

src/callbacks_step/stepsize.jl

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
1111
Set the time step size according to a CFL condition with CFL number `cfl`
1212
if the time integration method isn't adaptive itself.
13+
14+
The supplied keyword argument `cfl` must be either a `Real` number or
15+
a function of time `t` returning a `Real` number.
1316
"""
14-
mutable struct StepsizeCallback{RealT}
15-
cfl_number::RealT
17+
mutable struct StepsizeCallback{CflType}
18+
cfl_number::CflType
1619
end
1720

1821
function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:StepsizeCallback})
@@ -39,8 +42,8 @@ function Base.show(io::IO, ::MIME"text/plain",
3942
end
4043
end
4144

42-
function StepsizeCallback(; cfl::Real = 1.0)
43-
stepsize_callback = StepsizeCallback(cfl)
45+
function StepsizeCallback(; cfl = 1.0)
46+
stepsize_callback = StepsizeCallback{typeof(cfl)}(cfl)
4447

4548
DiscreteCallback(stepsize_callback, stepsize_callback, # the first one is the condition, the second the affect!
4649
save_positions = (false, false),
@@ -80,16 +83,6 @@ end
8083
return nothing
8184
end
8285

83-
# General case for a single semidiscretization
84-
function calculate_dt(u_ode, t, cfl_number, semi::AbstractSemidiscretization)
85-
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
86-
u = wrap_array(u_ode, mesh, equations, solver, cache)
87-
88-
dt = cfl_number * max_dt(u, t, mesh,
89-
have_constant_speed(equations), equations,
90-
solver, cache)
91-
end
92-
9386
# Time integration methods from the DiffEq ecosystem without adaptive time stepping on their own
9487
# such as `CarpenterKennedy2N54` require passing `dt=...` in `solve(ode, ...)`. Since we don't have
9588
# an integrator at this stage but only the ODE, this method will be used there. It's called in
@@ -103,11 +96,28 @@ function (cb::DiscreteCallback{Condition, Affect!})(ode::ODEProblem) where {Cond
10396
u_ode = ode.u0
10497
t = first(ode.tspan)
10598
semi = ode.p
99+
100+
dt = calculate_dt(u_ode, t, cfl_number, semi)
101+
end
102+
103+
# General case for a single (i.e., non-coupled) semidiscretization
104+
# Case for constant `cfl_number`.
105+
function calculate_dt(u_ode, t, cfl_number::Real, semi::AbstractSemidiscretization)
106106
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
107107
u = wrap_array(u_ode, mesh, equations, solver, cache)
108108

109-
return cfl_number *
110-
max_dt(u, t, mesh, have_constant_speed(equations), equations, solver, cache)
109+
dt = cfl_number * max_dt(u, t, mesh,
110+
have_constant_speed(equations), equations,
111+
solver, cache)
112+
end
113+
# Case for `cfl_number` as a function of time `t`.
114+
function calculate_dt(u_ode, t, cfl_number, semi::AbstractSemidiscretization)
115+
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
116+
u = wrap_array(u_ode, mesh, equations, solver, cache)
117+
118+
dt = cfl_number(t) * max_dt(u, t, mesh,
119+
have_constant_speed(equations), equations,
120+
solver, cache)
111121
end
112122

113123
include("stepsize_dg1d.jl")

src/semidiscretization/semidiscretization_coupled.jl

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,17 +351,26 @@ end
351351
################################################################################
352352

353353
# In case of coupled system, use minimum timestep over all systems
354-
function calculate_dt(u_ode, t, cfl_number, semi::SemidiscretizationCoupled)
354+
# Case for constant `cfl_number`.
355+
function calculate_dt(u_ode, t, cfl_number::Real, semi::SemidiscretizationCoupled)
355356
dt = minimum(eachsystem(semi)) do i
356357
u_ode_slice = get_system_u_ode(u_ode, i, semi)
357358
calculate_dt(u_ode_slice, t, cfl_number, semi.semis[i])
358359
end
359360

360361
return dt
361362
end
363+
# Case for `cfl_number` as a function of time `t`.
364+
function calculate_dt(u_ode, t, cfl_number, semi::SemidiscretizationCoupled)
365+
cfl_number_ = cfl_number(t)
366+
dt = minimum(eachsystem(semi)) do i
367+
u_ode_slice = get_system_u_ode(u_ode, i, semi)
368+
calculate_dt(u_ode_slice, t, cfl_number_, semi.semis[i])
369+
end
370+
end
362371

363372
function update_cleaning_speed!(semi_coupled::SemidiscretizationCoupled,
364-
glm_speed_callback, dt)
373+
glm_speed_callback, dt, t)
365374
@unpack glm_scale, cfl, semi_indices = glm_speed_callback
366375

367376
if length(semi_indices) == 0
@@ -376,12 +385,19 @@ function update_cleaning_speed!(semi_coupled::SemidiscretizationCoupled,
376385
end
377386
end
378387

388+
if cfl isa Real # Case for constant CFL
389+
cfl_number = cfl
390+
else # Variable CFL
391+
cfl_number = cfl(t)
392+
end
393+
379394
for semi_index in semi_indices
380395
semi = semi_coupled.semis[semi_index]
381396
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
382397

383398
# compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR)
384-
c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache)
399+
c_h_deltat = calc_dt_for_cleaning_speed(cfl_number,
400+
mesh, equations, solver, cache)
385401

386402
# c_h is proportional to its own time step divided by the complete MHD time step
387403
# We use @reset here since the equations are immutable (to work on GPUs etc.).

test/test_p4est_2d.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,41 @@ end
666666
end
667667
end
668668

669+
@trixi_testset "elixir_mhd_rotor_cfl_ramp.jl" begin
670+
@test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_rotor_cfl_ramp.jl"),
671+
l2=[
672+
0.45519051169507474,
673+
0.8917985468745363,
674+
0.8324681609772325,
675+
0.0,
676+
0.9801426190285389,
677+
0.10476233464125001,
678+
0.15551270692826116,
679+
0.0,
680+
2.0201603821472296e-5
681+
],
682+
linf=[
683+
10.196786739705292,
684+
18.267539012179128,
685+
10.046104290498878,
686+
0.0,
687+
19.668302849210974,
688+
1.395022093528294,
689+
1.8717844606331189,
690+
0.0,
691+
0.001651262488701531
692+
],
693+
tspan=(0.0, 0.02))
694+
# Ensure that we do not have excessive memory allocations
695+
# (e.g., from type instabilities)
696+
let
697+
t = sol.t[end]
698+
u_ode = sol.u[end]
699+
du_ode = similar(u_ode)
700+
@test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000
701+
end
702+
end
703+
669704
@trixi_testset "elixir_linearizedeuler_gaussian_source.jl" begin
670705
@test_trixi_include(joinpath(EXAMPLES_DIR,
671706
"elixir_linearizedeuler_gaussian_source.jl"),

0 commit comments

Comments
 (0)