Skip to content

Commit cdd552f

Browse files
Entropy bounded limiter (#2078)
* start working * get EB limiter to work * Refine EB limiter * fmt * 2D/3D Limiters * introduce parameter of tolerated entropy decrease * change to exp_entropy_diff * fmt * export * docstring * 3d example * exam,ples * examples * examples * fmt * adaptive * examples * fmt * tests + docu * fmt + spelling * comment * Apply suggestions from code review Co-authored-by: Benjamin Bolm <[email protected]> * Apply suggestions from code review * increase => change * fmt * spelling * revisit * revert * back to decrease * fmt * typo * comments * theta * longer sim * removed unused code * for coverage * Apply suggestions from code review * longer runtime for coverage * vals * reproducible test values * shorten * changes * testvals * test vals * Update src/callbacks_stage/entropy_bounded_limiter_2d.jl * Apply suggestions from code review * Update docs/literate/src/files/shock_capturing.jl * more iters * Apply suggestions from code review * comments * mention step_limiter! capability * Update docs/literate/src/files/shock_capturing.jl * Update src/callbacks_stage/entropy_bounded_limiter.jl * Update src/callbacks_stage/entropy_bounded_limiter.jl * Apply suggestions from code review --------- Co-authored-by: Benjamin Bolm <[email protected]>
1 parent a05a68d commit cdd552f

15 files changed

+836
-7
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ installation and postprocessing procedures. Its features include:
3636
* Discontinuous Galerkin methods
3737
* Kinetic energy-preserving and entropy-stable methods based on flux differencing
3838
* Entropy-stable shock capturing
39+
* [Finite difference summation by parts (SBP) methods](https://github.com/ranocha/SummationByPartsOperators.jl)
40+
* Advanced limiting strategies
3941
* Positivity-preserving limiting
4042
* Subcell invariant domain-preserving (IDP) limiting
41-
* [Finite difference summation by parts (SBP) methods](https://github.com/ranocha/SummationByPartsOperators.jl)
43+
* Entropy-bounded limiting
4244
* Compatible with the [SciML ecosystem for ordinary differential equations](https://diffeq.sciml.ai/latest/)
4345
* [Explicit low-storage Runge-Kutta time integration](https://diffeq.sciml.ai/latest/solvers/ode_solve/#Low-Storage-Methods)
4446
* [Strong stability preserving methods](https://diffeq.sciml.ai/latest/solvers/ode_solve/#Explicit-Strong-Stability-Preserving-Runge-Kutta-Methods-for-Hyperbolic-PDEs-(Conservation-Laws))

docs/literate/src/files/shock_capturing.jl

Lines changed: 136 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,19 @@
9696
# [Zhang, Shu (2011)](https://doi.org/10.1098/rspa.2011.0153).
9797

9898
# It works the following way. For every passed (scalar) variable and for every DG element we calculate
99-
# the minimal value $value_{min}$. If this value falls below the given threshold $\varepsilon$,
99+
# the minimal value $value_\text{min}$. If this value falls below the given threshold $\varepsilon$,
100100
# the approximation is slightly adapted such that the minimal value of the relevant variable lies
101101
# now above the threshold.
102102
# ```math
103-
# \underline{u}^{new} = \theta * \underline{u} + (1-\theta) * u_{mean}
103+
# \underline{u}^\text{new} = \theta * \underline{u} + (1-\theta) * u_\text{mean}
104104
# ```
105105
# where $\underline{u}$ are the collected pointwise evaluation coefficients in element $e$ and
106-
# $u_{mean}$ the integral mean of the quantity in $e$. The new coefficients are a convex combination
106+
# $u_\text{mean}$ the integral mean of the quantity in $e$. The new coefficients are a convex combination
107107
# of these two values with factor
108108
# ```math
109-
# \theta = \frac{value_{mean} - \varepsilon}{value_{mean} - value_{min}},
109+
# \theta = \frac{value_\text{mean} - \varepsilon}{value_\text{mean} - value_\text{min}},
110110
# ```
111-
# where $value_{mean}$ is the relevant variable evaluated for the mean value $u_{mean}$.
111+
# where $value_\text{mean}$ is the relevant variable evaluated for the mean value $u_\text{mean}$.
112112

113113
# The adapted approximation keeps the exact same mean value, but the relevant variable is now greater
114114
# or equal the threshold $\varepsilon$ at every node in every element.
@@ -225,6 +225,137 @@ sol = solve(ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition=false
225225
using Plots
226226
plot(sol)
227227

228+
# # Entropy bounded limiter
229+
230+
# As argued in the description of the positivity preserving limiter above it might sometimes be
231+
# necessary to apply advanced techniques to ensure a physically meaningful solution.
232+
# Apart from the positivity of pressure and density, the physical entropy of the system should increase
233+
# over the course of a simulation, see e.g. [this](https://doi.org/10.1016/0168-9274(86)90029-2) paper by Tadmor where this property is
234+
# shown for the compressible Euler equations.
235+
# As this is not necessarily the case for the numerical approximation (especially for the high-order, non-diffusive DG discretizations),
236+
# Lv and Ihme devised an a-posteriori limiter in [this paper](https://doi.org/10.1016/j.jcp.2015.04.026) which can be applied after each Runge-Kutta stage.
237+
# This limiter enforces a non-decrease in the physical, thermodynamic entropy $S$
238+
# by bounding the entropy decrease (entropy increase is always tolerated) $\Delta S$ in each grid cell.
239+
#
240+
# This translates into a requirement that the entropy of the limited approximation $S\Big(\mathcal{L}\big[\boldsymbol u(\Delta t) \big] \Big)$ should be
241+
# greater or equal than the previous iterates' entropy $S\big(\boldsymbol u(0) \big)$, enforced at each quadrature point:
242+
# ```math
243+
# S\Big(\mathcal{L}\big[\boldsymbol u(\Delta t, \boldsymbol{x}_i) \big] \Big) \overset{!}{\geq} S\big(\boldsymbol u(0, \boldsymbol{x}_i) \big), \quad i = 1, \dots, (k+1)^d
244+
# ```
245+
# where $k$ denotes the polynomial degree of the element-wise solution and $d$ is the spatial dimension.
246+
# For an ideal gas, the thermodynamic entropy $S(\boldsymbol u) = S(p, \rho)$ is given by
247+
# ```math
248+
# S(p, \rho) = \ln \left( \frac{p}{\rho^\gamma} \right) \: .
249+
# ```
250+
# Thus, the non-decrease in entropy can be reformulated as
251+
# ```math
252+
# p(\boldsymbol{x}_i) - e^{ S\big(\boldsymbol u(0, \boldsymbol{x}_i) \big)} \cdot \rho(\boldsymbol{x}_i)^\gamma \overset{!}{\geq} 0, \quad i = 1, \dots, (k+1)^d \: .
253+
# ```
254+
# In a practical simulation, we might tolerate a maximum (exponentiated) entropy decrease per element, i.e.,
255+
# ```math
256+
# \Delta e^S \coloneqq \min_{i} \left\{ p(\boldsymbol{x}_i) - e^{ S\big(\boldsymbol u(0, \boldsymbol{x}_i) \big)} \cdot \rho(\boldsymbol{x}_i)^\gamma \right\} < c
257+
# ```
258+
# with hyper-parameter $c$ which is to be specified by the user.
259+
# The default value for the corresponding parameter $c=$ `exp_entropy_decrease_max` is set to $-10^{-13}$, i.e., slightly less than zero to
260+
# avoid spurious limiter actions for cells in which the entropy remains effectively constant.
261+
# Other values can be specified by setting the `exp_entropy_decrease_max` keyword in the constructor of the limiter:
262+
# ```julia
263+
# stage_limiter! = EntropyBoundedLimiter(exp_entropy_decrease_max=-1e-9)
264+
# ```
265+
# Smaller values (larger in absolute value) for `exp_entropy_decrease_max` relax the entropy increase requirement and are thus less diffusive.
266+
# On the other hand, for larger values (smaller in absolute value) of `exp_entropy_decrease_max` the limiter acts more often and the solution becomes more diffusive.
267+
#
268+
# In particular, we compute again a limiting parameter $\vartheta \in [0, 1]$ which is then used to blend the
269+
# unlimited nodal values $\boldsymbol u$ with the mean value $\boldsymbol u_{\text{mean}}$ of the element:
270+
# ```math
271+
# \mathcal{L} [\boldsymbol u](\vartheta) \coloneqq (1 - \vartheta) \boldsymbol u + \vartheta \cdot \boldsymbol u_{\text{mean}}
272+
# ```
273+
# For the exact definition of $\vartheta$ the interested user is referred to section 4.4 of the paper by Lv and Ihme.
274+
# Note that therein the limiting parameter is denoted by $\epsilon$, which is not to be confused with the threshold $\varepsilon$ of the Zhang-Shu limiter.
275+
276+
# As for the positivity preserving limiter, the entropy bounded limiter may be applied after every Runge-Kutta stage.
277+
# Both fixed timestep methods such as [`CarpenterKennedy2N54`](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) and
278+
# adaptive timestep methods such as [`SSPRK43`](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) are supported.
279+
# We would like to remark that of course every `stage_limiter!` can also be used as a `step_limiter!`, i.e.,
280+
# acting only after the full time step has been taken.
281+
282+
# As an example, we consider a variant of the [1D medium blast wave example](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/tree_1d_dgsem/elixir_euler_blast_wave.jl)
283+
# wherein the shock capturing method discussed above is employed to handle the shock.
284+
# Here, we use a standard DG solver with HLLC surface flux:
285+
using Trixi
286+
287+
solver = DGSEM(polydeg = 3, surface_flux = flux_hllc)
288+
289+
# The remaining setup is the same as in the standard example:
290+
291+
using OrdinaryDiffEq
292+
293+
###############################################################################
294+
# semidiscretization of the compressible Euler equations
295+
296+
equations = CompressibleEulerEquations1D(1.4)
297+
298+
function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations1D)
299+
## Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave"
300+
## Set up polar coordinates
301+
inicenter = SVector(0.0)
302+
x_norm = x[1] - inicenter[1]
303+
r = abs(x_norm)
304+
## The following code is equivalent to
305+
## phi = atan(0.0, x_norm)
306+
## cos_phi = cos(phi)
307+
## in 1D but faster
308+
cos_phi = x_norm > 0 ? one(x_norm) : -one(x_norm)
309+
310+
## Calculate primitive variables
311+
rho = r > 0.5 ? 1.0 : 1.1691
312+
v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi
313+
p = r > 0.5 ? 1.0E-3 : 1.245
314+
315+
return prim2cons(SVector(rho, v1, p), equations)
316+
end
317+
initial_condition = initial_condition_blast_wave
318+
319+
coordinates_min = (-2.0,)
320+
coordinates_max = (2.0,)
321+
mesh = TreeMesh(coordinates_min, coordinates_max,
322+
initial_refinement_level = 6,
323+
n_cells_max = 10_000)
324+
325+
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver)
326+
327+
###############################################################################
328+
# ODE solvers, callbacks etc.
329+
330+
tspan = (0.0, 12.5)
331+
ode = semidiscretize(semi, tspan)
332+
333+
summary_callback = SummaryCallback()
334+
335+
analysis_interval = 100
336+
337+
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
338+
339+
alive_callback = AliveCallback(analysis_interval = analysis_interval)
340+
341+
stepsize_callback = StepsizeCallback(cfl = 0.5)
342+
343+
callbacks = CallbackSet(summary_callback,
344+
analysis_callback, alive_callback,
345+
stepsize_callback)
346+
347+
# We specify the `stage_limiter!` supplied to the classic SSPRK33 integrator
348+
# with strict entropy increase enforcement
349+
# (recall that the tolerated exponentiated entropy decrease is set to -1e-13).
350+
stage_limiter! = EntropyBoundedLimiter()
351+
352+
# We run the simulation with the SSPRK33 method and the entropy bounded limiter:
353+
sol = solve(ode, SSPRK33(stage_limiter!);
354+
dt = 1.0,
355+
callback = callbacks);
356+
357+
using Plots
358+
plot(sol)
228359

229360
# ## Package versions
230361

docs/src/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ installation and postprocessing procedures. Its features include:
3232
* Positivity-preserving limiting
3333
* Subcell invariant domain-preserving (IDP) limiting
3434
* [Finite difference summation by parts (SBP) methods](https://github.com/ranocha/SummationByPartsOperators.jl)
35+
* Advanced limiting strategies
36+
* Positivity-preserving limiting
37+
* Subcell invariant domain-preserving (IDP) limiting
38+
* Entropy-bounded limiting
3539
* Compatible with the [SciML ecosystem for ordinary differential equations](https://diffeq.sciml.ai/latest/)
3640
* [Explicit low-storage Runge-Kutta time integration](https://diffeq.sciml.ai/latest/solvers/ode_solve/#Low-Storage-Methods)
3741
* [Strong stability preserving methods](https://diffeq.sciml.ai/latest/solvers/ode_solve/#Explicit-Strong-Stability-Preserving-Runge-Kutta-Methods-for-Hyperbolic-PDEs-(Conservation-Laws))
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
2+
using OrdinaryDiffEq
3+
using Trixi
4+
5+
###############################################################################
6+
# semidiscretization of the compressible ideal GLM-MHD equations
7+
8+
equations = IdealGlmMhdEquations3D(1.4)
9+
10+
"""
11+
initial_condition_blast_wave(x, t, equations::IdealGlmMhdEquations3D)
12+
13+
Weak magnetic blast wave setup taken from Section 6.1 of the paper:
14+
- A. M. Rueda-Ramírez, S. Hennemann, F. J. Hindenlang, A. R. Winters, G. J. Gassner (2021)
15+
An entropy stable nodal discontinuous Galerkin method for the resistive MHD
16+
equations. Part II: Subcell finite volume shock capturing
17+
[doi: 10.1016/j.jcp.2021.110580](https://doi.org/10.1016/j.jcp.2021.110580)
18+
"""
19+
function initial_condition_blast_wave(x, t, equations::IdealGlmMhdEquations3D)
20+
# Center of the blast wave is selected for the domain [0, 3]^3
21+
inicenter = (1.5, 1.5, 1.5)
22+
x_norm = x[1] - inicenter[1]
23+
y_norm = x[2] - inicenter[2]
24+
z_norm = x[3] - inicenter[3]
25+
r = sqrt(x_norm^2 + y_norm^2 + z_norm^2)
26+
27+
delta_0 = 0.1
28+
r_0 = 0.3
29+
lambda = exp(5.0 / delta_0 * (r - r_0))
30+
31+
prim_inner = SVector(1.2, 0.1, 0.0, 0.1, 0.9, 1.0, 1.0, 1.0, 0.0)
32+
prim_outer = SVector(1.2, 0.2, -0.4, 0.2, 0.3, 1.0, 1.0, 1.0, 0.0)
33+
prim_vars = (prim_inner + lambda * prim_outer) / (1.0 + lambda)
34+
35+
return prim2cons(prim_vars, equations)
36+
end
37+
initial_condition = initial_condition_blast_wave
38+
39+
surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell)
40+
volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell)
41+
polydeg = 3
42+
volume_integral = VolumeIntegralFluxDifferencing(volume_flux)
43+
44+
solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux,
45+
volume_integral = volume_integral)
46+
47+
# Mapping as described in https://arxiv.org/abs/2012.12040 but with slightly less warping.
48+
# The mapping will be interpolated at tree level, and then refined without changing
49+
# the geometry interpolant.
50+
function mapping(xi_, eta_, zeta_)
51+
# Transform input variables between -1 and 1 onto [0,3]
52+
xi = 1.5 * xi_ + 1.5
53+
eta = 1.5 * eta_ + 1.5
54+
zeta = 1.5 * zeta_ + 1.5
55+
56+
y = eta +
57+
3 / 11 * (cos(1.5 * pi * (2 * xi - 3) / 3) *
58+
cos(0.5 * pi * (2 * eta - 3) / 3) *
59+
cos(0.5 * pi * (2 * zeta - 3) / 3))
60+
61+
x = xi +
62+
3 / 11 * (cos(0.5 * pi * (2 * xi - 3) / 3) *
63+
cos(2 * pi * (2 * y - 3) / 3) *
64+
cos(0.5 * pi * (2 * zeta - 3) / 3))
65+
66+
z = zeta +
67+
3 / 11 * (cos(0.5 * pi * (2 * x - 3) / 3) *
68+
cos(pi * (2 * y - 3) / 3) *
69+
cos(0.5 * pi * (2 * zeta - 3) / 3))
70+
71+
return SVector(x, y, z)
72+
end
73+
74+
trees_per_dimension = (2, 2, 2)
75+
mesh = P4estMesh(trees_per_dimension,
76+
polydeg = 3,
77+
mapping = mapping,
78+
initial_refinement_level = 2,
79+
periodicity = true)
80+
81+
# create the semi discretization object
82+
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver)
83+
84+
###############################################################################
85+
# ODE solvers, callbacks etc.
86+
87+
tspan = (0.0, 0.5)
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+
amr_indicator = IndicatorLöhner(semi,
98+
variable = density_pressure)
99+
amr_controller = ControllerThreeLevel(semi, amr_indicator,
100+
base_level = 2,
101+
max_level = 4, max_threshold = 0.15)
102+
amr_callback = AMRCallback(semi, amr_controller,
103+
interval = 5,
104+
adapt_initial_condition = true,
105+
adapt_initial_condition_only_refine = true)
106+
107+
cfl = 3.0
108+
stepsize_callback = StepsizeCallback(cfl = cfl)
109+
110+
glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl)
111+
112+
callbacks = CallbackSet(summary_callback,
113+
analysis_callback,
114+
alive_callback,
115+
amr_callback,
116+
stepsize_callback,
117+
glm_speed_callback)
118+
119+
# Stage limiter required for this high CFL
120+
stage_limiter! = EntropyBoundedLimiter(exp_entropy_decrease_max = -5e-3)
121+
122+
###############################################################################
123+
# run the simulation
124+
125+
sol = solve(ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false),
126+
dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
127+
save_everystep = false, callback = callbacks);
128+
summary_callback() # print the timer summary
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
using OrdinaryDiffEq
3+
using Trixi
4+
5+
###############################################################################
6+
# semidiscretization of the compressible Euler equations
7+
8+
equations = CompressibleEulerEquations1D(1.4)
9+
10+
"""
11+
initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations1D)
12+
13+
A medium blast wave taken from
14+
- Sebastian Hennemann, Gregor J. Gassner (2020)
15+
A provably entropy stable subcell shock capturing approach for high order split form DG
16+
[arXiv: 2008.12044](https://arxiv.org/abs/2008.12044)
17+
"""
18+
function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations1D)
19+
# Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave"
20+
# Set up polar coordinates
21+
inicenter = SVector(0.0)
22+
x_norm = x[1] - inicenter[1]
23+
r = abs(x_norm)
24+
# The following code is equivalent to
25+
# phi = atan(0.0, x_norm)
26+
# cos_phi = cos(phi)
27+
# in 1D but faster
28+
cos_phi = x_norm > 0 ? one(x_norm) : -one(x_norm)
29+
30+
# Calculate primitive variables
31+
rho = r > 0.5 ? 1.0 : 1.1691
32+
v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi
33+
p = r > 0.5 ? 1.0E-3 : 1.245
34+
35+
return prim2cons(SVector(rho, v1, p), equations)
36+
end
37+
initial_condition = initial_condition_blast_wave
38+
39+
# Note: We do not need to use the shock-capturing methodology here,
40+
# in contrast to the standard `euler_blast_wave.jl` example.
41+
solver = DGSEM(polydeg = 3, surface_flux = flux_hllc)
42+
43+
coordinates_min = (-2.0,)
44+
coordinates_max = (2.0,)
45+
mesh = TreeMesh(coordinates_min, coordinates_max,
46+
initial_refinement_level = 6,
47+
n_cells_max = 10_000)
48+
49+
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver)
50+
51+
###############################################################################
52+
# ODE solvers, callbacks etc.
53+
54+
tspan = (0.0, 12.5)
55+
ode = semidiscretize(semi, tspan)
56+
57+
summary_callback = SummaryCallback()
58+
59+
analysis_interval = 100
60+
61+
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
62+
63+
alive_callback = AliveCallback(analysis_interval = analysis_interval)
64+
65+
stepsize_callback = StepsizeCallback(cfl = 0.5)
66+
67+
callbacks = CallbackSet(summary_callback,
68+
analysis_callback, alive_callback,
69+
stepsize_callback)
70+
71+
stage_limiter! = EntropyBoundedLimiter()
72+
73+
###############################################################################
74+
# run the simulation
75+
76+
sol = solve(ode, SSPRK33(stage_limiter!);
77+
dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
78+
callback = callbacks);
79+
80+
summary_callback() # print the timer summary

0 commit comments

Comments
 (0)