Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/docs/literate/src/files/shock_capturing.jl
5591 views
1
#src # Shock capturing with flux differencing and stage limiter
2
3
# This tutorial contains a short summary of the idea of shock capturing for DGSEM with flux differencing
4
# and its implementation in [Trixi.jl](https://github.com/trixi-framework/Trixi.jl).
5
# In the second part, an implementation of a positivity preserving limiter is added to the simulation.
6
7
# # Shock capturing with flux differencing
8
9
# The following rough explanation is on a very basic level. More information about an entropy stable
10
# shock-capturing strategy for DGSEM discretizations of advection dominated problems, such as the
11
# compressible Euler equations or the compressible Navier-Stokes equations, can be found in
12
# [Hennemann et al. (2021)](https://doi.org/10.1016/j.jcp.2020.109935). In
13
# [Rueda-Ramírez et al. (2021)](https://doi.org/10.1016/j.jcp.2021.110580) you find the extension to
14
# the systems with non-conservative terms, such as the compressible magnetohydrodynamics (MHD) equations.
15
# Furthermore, the latter paper introduces also second-order subcell finite volume stabilization.
16
17
# The strategy for a shock-capturing method presented by Hennemann et al. is based on a hybrid blending
18
# of a high-order DG method with a low-order variant. The low-order subcell first order finite volume (FV) method is created
19
# directly with the Legendre-Gauss-Lobatto (LGL) nodes already used for the high-order DGSEM.
20
# Then, the final method is a convex combination with regulating indicator $\alpha$ of these two methods.
21
22
# Since the surface integral is equal for both the DG and the subcell FV method, only the volume integral divides
23
# between the two methods.
24
25
# This strategy for the volume integral is implemented in Trixi.jl under the name of
26
# [`VolumeIntegralShockCapturingHG`](@ref) with the three parameters of the indicator and the volume fluxes for
27
# the DG and the subcell FV method.
28
29
# Note, that the DG method is based on the flux differencing formulation. Hence, you have to use a
30
# two-point flux, such as [`flux_ranocha`](@ref), [`flux_shima_etal`](@ref), [`flux_chandrashekar`](@ref) or [`flux_kennedy_gruber`](@ref),
31
# for the DG volume flux. We would recommend to use the entropy conserving flux `flux_ranocha` by
32
# [Ranocha (2018)](https://cuvillier.de/en/shop/publications/7743) for the compressible Euler equations.
33
# ````julia
34
# volume_integral = VolumeIntegralShockCapturingHG(indicator_sc;
35
# volume_flux_dg=volume_flux_dg,
36
# volume_flux_fv=volume_flux_fv)
37
# ````
38
39
# The volume integral employing second-order FV stabilization is constructed similarly:
40
# ````julia
41
# volume_integral = VolumeIntegralShockCapturingRRG(basis, indicator_sc;
42
# volume_flux_dg=flux_central,
43
# volume_flux_fv=flux_lax_friedrichs,
44
# slope_limiter=minmod)
45
# ````
46
# In addition to the parameters of the HG method, the DG `basis` **must** be supplied here.
47
# The `slope_limiter` keyword argument is optional and defaults to `minmod`.
48
# A list of supported slope limiters can be found in the reference of [`VolumeIntegralShockCapturingRRG`](@ref).
49
50
# We now focus on a choice of the shock capturing indicator `indicator_sc`, which can be used for both
51
# the first-order and the second-order FV stabilization.
52
# A possible indicator is $\alpha_{HG}$ presented by Hennemann et al. (p.10), which depends on the
53
# current approximation with modal coefficients $\{m_j\}_{j=0}^N$ of a given `variable`.
54
55
# The indicator is calculated for every DG element by itself. First, we calculate a smooth $\alpha$ by
56
# ```math
57
# \alpha = \frac{1}{1+\exp(-\frac{s}{\mathbb{T}}(\mathbb{E}-\mathbb{T}))}
58
# ```
59
# with the total energy $\mathbb{E}=\max\big(\frac{m_N^2}{\sum_{j=0}^N m_j^2}, \frac{m_{N-1}^2}{\sum_{j=0}^{N-1} m_j^2}\big)$,
60
# threshold $\mathbb{T}= 0.5 * 10^{-1.8*(N+1)^{1/4}}$ and parameter $s=\ln\big(\frac{1-0.0001}{0.0001}\big)\approx 9.21024$.
61
62
# For computational efficiency, $\alpha_{min}$ is introduced and used for
63
# ```math
64
# \tilde{\alpha} = \begin{cases}
65
# 0, & \text{if } \alpha<\alpha_{min}\\
66
# \alpha, & \text{if } \alpha_{min}\leq \alpha \leq 1- \alpha_{min}\\
67
# 1, & \text{if } 1-\alpha_{min}<\alpha.
68
# \end{cases}
69
# ```
70
71
# Moreover, the parameter $\alpha_{max}$ sets a maximal value for $\alpha$ by
72
# ```math
73
# \alpha = \min\{\tilde{\alpha}, \alpha_{max}\}.
74
# ```
75
# This allows to control the maximal dissipation.
76
77
# To remove numerical artifact the final indicator is smoothed with all the neighboring elements'
78
# indicators. This is activated with `alpha_smooth=true`.
79
# ```math
80
# \alpha_{HG} = \max_E \{ \alpha, 0.5 * \alpha_E\},
81
# ```
82
# where $E$ are all elements sharing a face with the current element.
83
84
# Furthermore, you can specify the variable used for the calculation. For instance you can choose
85
# `density`, `pressure` or both with `density_pressure` for the compressible Euler equations.
86
# For every equation there is also the option to use the first conservation variable with `first`.
87
88
# This indicator is implemented in Trixi.jl and called [`IndicatorHennemannGassner`](@ref) with the parameters
89
# `equations`, `basis`, `alpha_max`, `alpha_min`, `alpha_smooth` and `variable`.
90
# ````julia
91
# indicator_sc = IndicatorHennemannGassner(equations, basis,
92
# alpha_max=0.5,
93
# alpha_min=0.001,
94
# alpha_smooth=true,
95
# variable=variable)
96
# ````
97
98
# # Positivity preserving limiter
99
100
# Some numerical solutions are physically meaningless, for instance negative values of pressure
101
# or density for the compressible Euler equations. This often results in crashed simulations since
102
# the calculation of numerical fluxes or stable time steps uses mathematical operations like roots or
103
# logarithms. One option to avoid these cases are a-posteriori positivity preserving limiters.
104
# Trixi.jl provides the fully-discrete positivity-preserving limiter of
105
# [Zhang, Shu (2011)](https://doi.org/10.1098/rspa.2011.0153).
106
107
# It works the following way. For every passed (scalar) variable and for every DG element we calculate
108
# the minimal value $value_\text{min}$. If this value falls below the given threshold $\varepsilon$,
109
# the approximation is slightly adapted such that the minimal value of the relevant variable lies
110
# now above the threshold.
111
# ```math
112
# \underline{u}^\text{new} = \theta * \underline{u} + (1-\theta) * u_\text{mean}
113
# ```
114
# where $\underline{u}$ are the collected pointwise evaluation coefficients in element $e$ and
115
# $u_\text{mean}$ the integral mean of the quantity in $e$. The new coefficients are a convex combination
116
# of these two values with factor
117
# ```math
118
# \theta = \frac{value_\text{mean} - \varepsilon}{value_\text{mean} - value_\text{min}},
119
# ```
120
# where $value_\text{mean}$ is the relevant variable evaluated for the mean value $u_\text{mean}$.
121
122
# The adapted approximation keeps the exact same mean value, but the relevant variable is now greater
123
# or equal the threshold $\varepsilon$ at every node in every element.
124
125
# We specify the variables the way we did before for the shock capturing variables. For the
126
# compressible Euler equations `density`, `pressure` or the combined variable `density_pressure`
127
# are a reasonable choice.
128
129
# You can implement the limiter in Trixi.jl using [`PositivityPreservingLimiterZhangShu`](@ref) with parameters
130
# `threshold` and `variables`.
131
# ````julia
132
# stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds=thresholds,
133
# variables=variables)
134
# ````
135
# Then, the limiter is added to the time integration method in the `solve` function. For instance, like
136
# ````julia
137
# CarpenterKennedy2N54(stage_limiter!, williamson_condition=false)
138
# ````
139
# or
140
# ````julia
141
# SSPRK43(stage_limiter!).
142
# ````
143
144
# # Simulation with shock capturing and positivity preserving
145
146
# Now, we can run a simulation using the described methods of shock capturing and positivity
147
# preserving limiters. We want to give an example for the 2D compressible Euler equations.
148
using OrdinaryDiffEqLowStorageRK
149
using Trixi
150
151
equations = CompressibleEulerEquations2D(1.4)
152
153
# As our initial condition we use the Sedov blast wave setup.
154
function initial_condition_sedov_blast_wave(x, t, equations::CompressibleEulerEquations2D)
155
## Set up polar coordinates
156
inicenter = SVector(0.0, 0.0)
157
x_norm = x[1] - inicenter[1]
158
y_norm = x[2] - inicenter[2]
159
r = sqrt(x_norm^2 + y_norm^2)
160
161
r0 = 0.21875 # = 3.5 * smallest dx (for domain length=4 and max-ref=6)
162
## r0 = 0.5 # = more reasonable setup
163
E = 1.0
164
p0_inner = 3 * (equations.gamma - 1) * E / (3 * pi * r0^2)
165
p0_outer = 1.0e-5 # = true Sedov setup
166
## p0_outer = 1.0e-3 # = more reasonable setup
167
168
## Calculate primitive variables
169
rho = 1.0
170
v1 = 0.0
171
v2 = 0.0
172
p = r > r0 ? p0_outer : p0_inner
173
174
return prim2cons(SVector(rho, v1, v2, p), equations)
175
end
176
initial_condition = initial_condition_sedov_blast_wave
177
#-
178
basis = LobattoLegendreBasis(3)
179
180
# We set the numerical fluxes and divide between the surface flux and the two volume fluxes for the DG
181
# and FV method. Here, we are using [`flux_lax_friedrichs`](@ref) and [`flux_ranocha`](@ref).
182
surface_flux = flux_lax_friedrichs
183
volume_flux = flux_ranocha
184
185
# Now, we specify the shock capturing indicator $\alpha$.
186
187
# We implement the described indicator of Hennemann, Gassner as explained above with parameters
188
# `equations`, `basis`, `alpha_max`, `alpha_min`, `alpha_smooth` and `variable`.
189
# Since density and pressure are the critical variables in this example, we use
190
# `density_pressure = density * pressure = rho * p` as indicator variable.
191
indicator_sc = IndicatorHennemannGassner(equations, basis,
192
alpha_max = 0.5,
193
alpha_min = 0.001,
194
alpha_smooth = true,
195
variable = density_pressure)
196
197
# Now, we can use the defined fluxes and the indicator to implement the volume integral using shock
198
# capturing.
199
volume_integral = VolumeIntegralShockCapturingHG(indicator_sc;
200
volume_flux_dg = volume_flux,
201
volume_flux_fv = surface_flux)
202
203
# We finalize the discretization by implementing Trixi.jl's `solver`, `mesh`, `semi` and `ode`,
204
# while `solver` now has the extra parameter `volume_integral`.
205
solver = DGSEM(basis, surface_flux, volume_integral)
206
207
coordinates_min = (-2.0, -2.0)
208
coordinates_max = (2.0, 2.0)
209
mesh = TreeMesh(coordinates_min, coordinates_max,
210
initial_refinement_level = 6,
211
n_cells_max = 10_000,
212
periodicity = true)
213
214
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver;
215
boundary_conditions = boundary_condition_periodic)
216
217
tspan = (0.0, 1.0)
218
ode = semidiscretize(semi, tspan);
219
220
# We add some callbacks to get an solution analysis and use a CFL-based time step size calculation.
221
analysis_callback = AnalysisCallback(semi, interval = 100)
222
223
stepsize_callback = StepsizeCallback(cfl = 0.8)
224
225
callbacks = CallbackSet(analysis_callback, stepsize_callback);
226
227
# We now run the simulation using the positivity preserving limiter of Zhang and Shu for the variables
228
# density and pressure.
229
stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6),
230
variables = (Trixi.density, pressure))
231
232
sol = solve(ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false);
233
dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
234
ode_default_options()..., callback = callbacks);
235
236
using Plots
237
plot(sol)
238
239
# # Entropy bounded limiter
240
241
# As argued in the description of the positivity preserving limiter above it might sometimes be
242
# necessary to apply advanced techniques to ensure a physically meaningful solution.
243
# Apart from the positivity of pressure and density, the physical entropy of the system should increase
244
# 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
245
# shown for the compressible Euler equations.
246
# As this is not necessarily the case for the numerical approximation (especially for the high-order, non-diffusive DG discretizations),
247
# 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.
248
# This limiter enforces a non-decrease in the physical, thermodynamic entropy $S$
249
# by bounding the entropy decrease (entropy increase is always tolerated) $\Delta S$ in each grid cell.
250
#
251
# 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
252
# greater or equal than the previous iterates' entropy $S\big(\boldsymbol u(0) \big)$, enforced at each quadrature point:
253
# ```math
254
# 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
255
# ```
256
# where $k$ denotes the polynomial degree of the element-wise solution and $d$ is the spatial dimension.
257
# For an ideal gas, the thermodynamic entropy $S(\boldsymbol u) = S(p, \rho)$ is given by
258
# ```math
259
# S(p, \rho) = \ln \left( \frac{p}{\rho^\gamma} \right) \: .
260
# ```
261
# Thus, the non-decrease in entropy can be reformulated as
262
# ```math
263
# 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 \: .
264
# ```
265
# In a practical simulation, we might tolerate a maximum (exponentiated) entropy decrease per element, i.e.,
266
# ```math
267
# \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
268
# ```
269
# with hyper-parameter $c$ which is to be specified by the user.
270
# The default value for the corresponding parameter $c=$ `exp_entropy_decrease_max` is set to $-10^{-13}$, i.e., slightly less than zero to
271
# avoid spurious limiter actions for cells in which the entropy remains effectively constant.
272
# Other values can be specified by setting the `exp_entropy_decrease_max` keyword in the constructor of the limiter:
273
# ````julia
274
# stage_limiter! = EntropyBoundedLimiter(exp_entropy_decrease_max=-1e-9)
275
# ````
276
# Smaller values (larger in absolute value) for `exp_entropy_decrease_max` relax the entropy increase requirement and are thus less diffusive.
277
# 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.
278
#
279
# In particular, we compute again a limiting parameter $\vartheta \in [0, 1]$ which is then used to blend the
280
# unlimited nodal values $\boldsymbol u$ with the mean value $\boldsymbol u_{\text{mean}}$ of the element:
281
# ```math
282
# \mathcal{L} [\boldsymbol u](\vartheta) \coloneqq (1 - \vartheta) \boldsymbol u + \vartheta \cdot \boldsymbol u_{\text{mean}}
283
# ```
284
# For the exact definition of $\vartheta$ the interested user is referred to section 4.4 of the paper by Lv and Ihme.
285
# 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.
286
287
# As for the positivity preserving limiter, the entropy bounded limiter may be applied after every Runge-Kutta stage.
288
# Both fixed timestep methods such as [`CarpenterKennedy2N54`](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) and
289
# adaptive timestep methods such as [`SSPRK43`](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) are supported.
290
# We would like to remark that of course every `stage_limiter!` can also be used as a `step_limiter!`, i.e.,
291
# acting only after the full time step has been taken.
292
293
# 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)
294
# wherein the shock capturing method discussed above is employed to handle the shock.
295
# Here, we use a standard DG solver with HLLC surface flux:
296
using Trixi
297
298
solver = DGSEM(polydeg = 3, surface_flux = flux_hllc)
299
300
# The remaining setup is the same as in the standard example:
301
302
using OrdinaryDiffEqSSPRK
303
304
###############################################################################
305
# semidiscretization of the compressible Euler equations
306
307
equations = CompressibleEulerEquations1D(1.4)
308
309
function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations1D)
310
## Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave"
311
## Set up polar coordinates
312
inicenter = SVector(0.0)
313
x_norm = x[1] - inicenter[1]
314
r = abs(x_norm)
315
## The following code is equivalent to
316
## phi = atan(0.0, x_norm)
317
## cos_phi = cos(phi)
318
## in 1D but faster
319
cos_phi = x_norm > 0 ? one(x_norm) : -one(x_norm)
320
321
## Calculate primitive variables
322
rho = r > 0.5 ? 1.0 : 1.1691
323
v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi
324
p = r > 0.5 ? 1.0E-3 : 1.245
325
326
return prim2cons(SVector(rho, v1, p), equations)
327
end
328
initial_condition = initial_condition_blast_wave
329
330
coordinates_min = (-2.0,)
331
coordinates_max = (2.0,)
332
mesh = TreeMesh(coordinates_min, coordinates_max,
333
initial_refinement_level = 6,
334
n_cells_max = 10_000,
335
periodicity = true)
336
337
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver;
338
boundary_conditions = boundary_condition_periodic)
339
340
###############################################################################
341
# ODE solvers, callbacks etc.
342
343
tspan = (0.0, 12.5)
344
ode = semidiscretize(semi, tspan)
345
346
summary_callback = SummaryCallback()
347
348
analysis_interval = 100
349
350
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
351
352
alive_callback = AliveCallback(analysis_interval = analysis_interval)
353
354
stepsize_callback = StepsizeCallback(cfl = 0.5)
355
356
callbacks = CallbackSet(summary_callback,
357
analysis_callback, alive_callback,
358
stepsize_callback)
359
360
# We specify the `stage_limiter!` supplied to the classic SSPRK33 integrator
361
# with strict entropy increase enforcement
362
# (recall that the tolerated exponentiated entropy decrease is set to -1e-13).
363
stage_limiter! = EntropyBoundedLimiter()
364
365
# We run the simulation with the SSPRK33 method and the entropy bounded limiter:
366
sol = solve(ode, SSPRK33(stage_limiter!);
367
dt = 1.0,
368
callback = callbacks);
369
370
using Plots
371
plot(sol)
372
373
# ## Package versions
374
375
# These results were obtained using the following versions.
376
377
using InteractiveUtils
378
versioninfo()
379
380
using Pkg
381
Pkg.status(["Trixi", "OrdinaryDiffEqLowStorageRK", "OrdinaryDiffEqSSPRK", "Plots"],
382
mode = PKGMODE_MANIFEST)
383
384