Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/docs/literate/src/files/subcell_shock_capturing.jl
5591 views
1
#src # Subcell limiting with the IDP Limiter
2
3
# In the previous tutorial, the element-wise limiting with [`IndicatorHennemannGassner`](@ref)
4
# and [`VolumeIntegralShockCapturingHG`](@ref) was explained. This tutorial contains a short
5
# introduction to the idea and implementation of subcell shock capturing approaches in Trixi.jl,
6
# which is also based on the DGSEM scheme in flux differencing formulation.
7
# Trixi.jl contains the a-posteriori invariant domain-preserving (IDP) limiter which was
8
# introduced by [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) and
9
# [Rueda-Ramírez, Pazner, Gassner (2022)](https://doi.org/10.1016/j.compfluid.2022.105627).
10
# It is a flux-corrected transport-type (FCT) limiter and is implemented using [`SubcellLimiterIDP`](@ref)
11
# and [`VolumeIntegralSubcellLimiting`](@ref).
12
# Since it is an a-posteriori limiter you have to apply a correction stage after each Runge-Kutta
13
# stage. This is done by passing the stage callback [`SubcellLimiterIDPCorrection`](@ref) to the
14
# time integration method.
15
16
# ## Time integration method
17
# As mentioned before, the IDP limiting is an a-posteriori limiter. Its limiting process
18
# guarantees the target bounds for an explicit (forward) Euler time step. To still achieve a
19
# high-order approximation, the implementation uses strong-stability preserving (SSP) Runge-Kutta
20
# methods, which can be written as convex combinations of forward Euler steps.
21
# As such, they preserve the convexity of convex functions and functionals, such as the TVD
22
# semi-norm and the maximum principle in 1D, for instance.
23
#-
24
# Since IDP/FCT limiting procedure operates on independent forward Euler steps, its
25
# a-posteriori correction stage is implemented as a stage callback that is triggered after each
26
# forward Euler step in an SSP Runge-Kutta method. Unfortunately, the `solve(...)` routines in
27
# [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl), typically employed for time
28
# integration in Trixi.jl, do not support this type of stage callback.
29
#-
30
# Therefore, subcell limiting with the IDP limiter requires the use of a Trixi-intern
31
# time integration SSPRK method called with
32
# ````julia
33
# Trixi.solve(ode, method(stage_callbacks = stage_callbacks); ...);
34
# ````
35
#-
36
# Right now, only the canonical three-stage, third-order SSPRK method (Shu-Osher)
37
# [`Trixi.SimpleSSPRK33`](@ref) is implemented.
38
39
# # [IDP Limiting](@id IDPLimiter)
40
# The implementation of the invariant domain preserving (IDP) limiting approach ([`SubcellLimiterIDP`](@ref))
41
# is based on [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) and
42
# [Rueda-Ramírez, Pazner, Gassner (2022)](https://doi.org/10.101/j.compfluid.2022.105627).
43
# It supports several types of limiting which are enabled by passing parameters individually.
44
45
# ### [Global bounds](@id global_bounds)
46
# If enabled, the global bounds enforce physical admissibility conditions, such as non-negativity
47
# of variables. This can be done for conservative variables, where the limiter is of a one-sided
48
# Zalesak-type ([Zalesak, 1979](https://doi.org/10.1016/0021-9991(79)90051-2)), and general
49
# non-linear variables, where a Newton-bisection algorithm is used to enforce the bounds.
50
51
# The Newton-bisection algorithm is an iterative method and requires some parameters.
52
# It uses a fixed maximum number of iteration steps (`max_iterations_newton = 10`) and
53
# relative/absolute tolerances (`newton_tolerances = (1.0e-12, 1.0e-14)`). The given values are
54
# sufficient in most cases and therefore used as default. If the implemented bounds checking
55
# functionality indicates problems with the limiting (see [below](@ref subcell_bounds_check))
56
# the Newton method with the chosen parameters might not manage to converge. If so, adapting
57
# the mentioned parameters helps fix that.
58
# Additionally, there is the parameter
59
# `gamma_constant_newton`, which can be used to scale the antidiffusive flux for the computation
60
# of the blending coefficients of nonlinear variables. The default value is `2 * ndims(equations)`,
61
# as it was shown by [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) [Section 4.2.2.]
62
# that this value guarantees the fulfillment of bounds for a forward-Euler increment.
63
64
# Very small non-negative values can be an issue as well. That's why we use an additional
65
# correction factor in the calculation of the global bounds,
66
# ```math
67
# u^{new} \geq \beta * u^{FV}.
68
# ```
69
# By default, $\beta$ (named `positivity_correction_factor`) is set to `0.1` which works properly
70
# in most of the tested setups.
71
72
# #### Conservative variables
73
# The procedure to enforce global bounds for a conservative variables is as follows:
74
# If you want to guarantee non-negativity for the density of the compressible Euler equations,
75
# you pass the specific quantity name of the conservative variable.
76
using Trixi
77
equations = CompressibleEulerEquations2D(1.4)
78
79
# The quantity name of the density is `rho` which is how we enable its limiting.
80
positivity_variables_cons = ["rho"]
81
82
# The quantity names are passed as a vector to allow several quantities.
83
# This is used, for instance, if you want to limit the density of two different components using
84
# the multicomponent compressible Euler equations.
85
equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.648),
86
gas_constants = (0.287, 1.578))
87
88
# Then, we just pass both quantity names.
89
positivity_variables_cons = ["rho1", "rho2"]
90
91
# Alternatively, it is possible to all limit all density variables with a general command using
92
positivity_variables_cons = ["rho" * string(i) for i in eachcomponent(equations)]
93
94
# #### Non-linear variables
95
# To allow limitation for all possible non-linear variables, including variables defined
96
# on-the-fly, you can directly pass the function that computes the quantity for which you want
97
# to enforce positivity. For instance, if you want to enforce non-negativity for the pressure,
98
# do as follows.
99
positivity_variables_nonlinear = [pressure]
100
101
# ### Local bounds
102
# Second, Trixi.jl supports the limiting with local bounds for conservative variables using a
103
# two-sided Zalesak-type limiter ([Zalesak, 1979](https://doi.org/10.1016/0021-9991(79)90051-2))
104
# and for general non-linear variables using a one-sided Newton-bisection algorithm.
105
# They allow to avoid spurious oscillations within the global bounds and to improve the
106
# shock-capturing capabilities of the method. The corresponding numerical admissibility conditions
107
# are frequently formulated as local maximum or minimum principles. The local bounds are computed
108
# using the maximum and minimum values of all local neighboring nodes. Within this calculation we
109
# use the low-order FV solution values for each node.
110
111
# As for the limiting with global bounds you are passing the quantity names of the conservative
112
# variables you want to limit. So, to limit the density with lower and upper local bounds pass
113
# the following.
114
local_twosided_variables_cons = ["rho"]
115
116
# To limit non-linear variables locally, pass the variable function combined with the requested
117
# bound (`min` or `max`) as a tuple. For instance, to impose a lower local bound on the modified
118
# specific entropy [`entropy_guermond_etal`](@ref), use
119
local_onesided_variables_nonlinear = [(entropy_guermond_etal, min)]
120
121
# ## Exemplary simulation
122
# How to set up a simulation using the IDP limiting becomes clearer when looking at an exemplary
123
# setup. This will be a simplified version of `tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell.jl`.
124
# Since the setup is mostly very similar to a pure DGSEM setup as in
125
# `tree_2d_dgsem/elixir_euler_blast_wave.jl`, the equivalent parts are used without any explanation
126
# here.
127
using Trixi
128
129
equations = CompressibleEulerEquations2D(1.4)
130
131
function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D)
132
## Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave"
133
## Set up polar coordinates
134
inicenter = SVector(0.0, 0.0)
135
x_norm = x[1] - inicenter[1]
136
y_norm = x[2] - inicenter[2]
137
r = sqrt(x_norm^2 + y_norm^2)
138
phi = atan(y_norm, x_norm)
139
sin_phi, cos_phi = sincos(phi)
140
141
## Calculate primitive variables
142
rho = r > 0.5 ? 1.0 : 1.1691
143
v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi
144
v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi
145
p = r > 0.5 ? 1.0E-3 : 1.245
146
147
return prim2cons(SVector(rho, v1, v2, p), equations)
148
end
149
initial_condition = initial_condition_blast_wave;
150
151
# Since the surface integral is equal for both the DG and the subcell FV method, the limiting is
152
# applied only in the volume integral.
153
#-
154
# Note, that the DG method is based on the flux differencing formulation. Hence, you have to use a
155
# two-point flux, such as [`flux_ranocha`](@ref), [`flux_shima_etal`](@ref), [`flux_chandrashekar`](@ref)
156
# or [`flux_kennedy_gruber`](@ref), for the DG volume flux.
157
surface_flux = flux_lax_friedrichs
158
volume_flux = flux_ranocha
159
160
# The limiter is implemented within [`SubcellLimiterIDP`](@ref). It always requires the
161
# parameters `equations` and `basis`. With additional parameters (described [above](@ref IDPLimiter)
162
# or listed in the docstring) you can specify and enable additional limiting options.
163
# Here, the simulation should contain local limiting for the density using lower and upper bounds.
164
basis = LobattoLegendreBasis(3)
165
limiter_idp = SubcellLimiterIDP(equations, basis;
166
local_twosided_variables_cons = ["rho"])
167
168
# The initialized limiter is passed to `VolumeIntegralSubcellLimiting` in addition to the volume
169
# fluxes of the low-order and high-order scheme.
170
volume_integral = VolumeIntegralSubcellLimiting(limiter_idp;
171
volume_flux_dg = volume_flux,
172
volume_flux_fv = surface_flux)
173
174
# Then, the volume integral is passed to `solver` as it is done for the standard flux-differencing
175
# DG scheme or the element-wise limiting.
176
solver = DGSEM(basis, surface_flux, volume_integral)
177
#-
178
coordinates_min = (-2.0, -2.0)
179
coordinates_max = (2.0, 2.0)
180
mesh = TreeMesh(coordinates_min, coordinates_max,
181
initial_refinement_level = 5,
182
n_cells_max = 10_000,
183
periodicity = true)
184
185
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver;
186
boundary_conditions = boundary_condition_periodic)
187
188
tspan = (0.0, 2.0)
189
ode = semidiscretize(semi, tspan)
190
191
summary_callback = SummaryCallback()
192
193
analysis_interval = 1000
194
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
195
196
alive_callback = AliveCallback(analysis_interval = analysis_interval)
197
198
save_solution = SaveSolutionCallback(interval = 1000,
199
save_initial_solution = true,
200
save_final_solution = true,
201
solution_variables = cons2prim)
202
203
stepsize_callback = StepsizeCallback(cfl = 0.3)
204
205
callbacks = CallbackSet(summary_callback,
206
analysis_callback, alive_callback,
207
save_solution,
208
stepsize_callback);
209
210
# As explained above, the IDP limiter works a-posteriori and requires the additional use of a
211
# correction stage implemented with the stage callback [`SubcellLimiterIDPCorrection`](@ref).
212
# This callback is passed within a tuple to the time integration method.
213
stage_callbacks = (SubcellLimiterIDPCorrection(),)
214
215
# Moreover, as mentioned before as well, simulations with subcell limiting require a Trixi-intern
216
# SSPRK time integration methods with passed stage callbacks and a Trixi-intern `Trixi.solve(...)`
217
# routine.
218
sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks);
219
dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
220
callback = callbacks);
221
222
# ## Visualization
223
# As for a standard simulation in Trixi.jl, it is possible to visualize the solution using the
224
# `plot` routine from Plots.jl.
225
using Plots
226
plot(sol)
227
228
# To get an additional look at the amount of limiting that is used, you can use the visualization
229
# approach using the [`SaveSolutionCallback`](@ref), [`Trixi2Vtk`](https://github.com/trixi-framework/Trixi2Vtk.jl)
230
# and [ParaView](https://www.paraview.org/download/). More details about this procedure
231
# can be found in the [visualization documentation](@ref visualization).
232
#-
233
# With that implementation and the standard procedure used for Trixi2Vtk you get the following
234
# dropdown menu in ParaView.
235
#-
236
# ![ParaView_Dropdownmenu](https://github.com/trixi-framework/Trixi.jl/assets/74359358/70d15f6a-059b-4349-8291-68d9ab3af43e)
237
238
# The resulting visualization of the density and the limiting parameter then looks like this.
239
# ![blast_wave_paraview](https://github.com/trixi-framework/Trixi.jl/assets/74359358/e5808bed-c8ab-43bf-af7a-050fe43dd630)
240
241
# You can see that the limiting coefficient does not lie in the interval [0,1] because Trixi2Vtk
242
# interpolates all quantities to regular nodes by default.
243
# You can disable this functionality with `reinterpolate=false` within the call of `trixi2vtk(...)`
244
# and get the following visualization.
245
# ![blast_wave_paraview_reinterpolate=false](https://github.com/trixi-framework/Trixi.jl/assets/74359358/39274f18-0064-469c-b4da-bac4b843e116)
246
247
# ## [Bounds checking](@id subcell_bounds_check)
248
# Subcell limiting is based on the fulfillment of target bounds - either global or local.
249
# Although the implementation works and has been thoroughly tested, there are some cases where
250
# these bounds are not met.
251
# For instance, the deviations could be in machine precision, which is not problematic.
252
# Larger deviations can be cause by too large time-step sizes (which can be easily fixed by
253
# reducing the CFL number), specific boundary conditions or source terms. Insufficient parameters
254
# for the Newton-bisection algorithm can also be a reason when limiting non-linear variables.
255
# There are described [above](@ref global_bounds).
256
#-
257
# In many cases, it is reasonable to monitor the bounds deviations.
258
# Because of that, Trixi.jl supports a bounds checking routine implemented using the stage
259
# callback [`BoundsCheckCallback`](@ref). It checks all target bounds for fulfillment
260
# in every RK stage. If added to the tuple of stage callbacks like
261
# ````julia
262
# stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback())
263
# ````
264
# and passed to the time integration method, a summary is added to the final console output.
265
# For the given example, this summary shows that all bounds are met at all times.
266
# ````
267
# ────────────────────────────────────────────────────────────────────────────────────────────────────
268
# Maximum deviation from bounds:
269
# ────────────────────────────────────────────────────────────────────────────────────────────────────
270
# rho:
271
# - lower bound: 0.0
272
# - upper bound: 0.0
273
# ────────────────────────────────────────────────────────────────────────────────────────────────────
274
# ````
275
276
# Moreover, it is also possible to monitor the bounds deviations incurred during the simulations.
277
# To do that use the parameter `save_errors = true`, such that the instant deviations are written
278
# to `deviations.txt` in `output_directory` every `interval` time steps.
279
# ````julia
280
# BoundsCheckCallback(save_errors = true, output_directory = "out", interval = 100)
281
# ````
282
# Then, for the given example the deviations file contains all daviations for the current
283
# timestep and simulation time.
284
# ````
285
# iter, simu_time, rho_min, rho_max
286
# 100, 0.29103427131404924, 0.0, 0.0
287
# 200, 0.5980281923063808, 0.0, 0.0
288
# 300, 0.9520853560765293, 0.0, 0.0
289
# 400, 1.3630295622683186, 0.0, 0.0
290
# 500, 1.8344999624013498, 0.0, 0.0
291
# 532, 1.9974179806990118, 0.0, 0.0
292
# ````
293
294