Path: blob/main/docs/literate/src/files/subcell_shock_capturing.jl
5591 views
#src # Subcell limiting with the IDP Limiter12# In the previous tutorial, the element-wise limiting with [`IndicatorHennemannGassner`](@ref)3# and [`VolumeIntegralShockCapturingHG`](@ref) was explained. This tutorial contains a short4# introduction to the idea and implementation of subcell shock capturing approaches in Trixi.jl,5# which is also based on the DGSEM scheme in flux differencing formulation.6# Trixi.jl contains the a-posteriori invariant domain-preserving (IDP) limiter which was7# introduced by [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) and8# [Rueda-Ramírez, Pazner, Gassner (2022)](https://doi.org/10.1016/j.compfluid.2022.105627).9# It is a flux-corrected transport-type (FCT) limiter and is implemented using [`SubcellLimiterIDP`](@ref)10# and [`VolumeIntegralSubcellLimiting`](@ref).11# Since it is an a-posteriori limiter you have to apply a correction stage after each Runge-Kutta12# stage. This is done by passing the stage callback [`SubcellLimiterIDPCorrection`](@ref) to the13# time integration method.1415# ## Time integration method16# As mentioned before, the IDP limiting is an a-posteriori limiter. Its limiting process17# guarantees the target bounds for an explicit (forward) Euler time step. To still achieve a18# high-order approximation, the implementation uses strong-stability preserving (SSP) Runge-Kutta19# methods, which can be written as convex combinations of forward Euler steps.20# As such, they preserve the convexity of convex functions and functionals, such as the TVD21# semi-norm and the maximum principle in 1D, for instance.22#-23# Since IDP/FCT limiting procedure operates on independent forward Euler steps, its24# a-posteriori correction stage is implemented as a stage callback that is triggered after each25# forward Euler step in an SSP Runge-Kutta method. Unfortunately, the `solve(...)` routines in26# [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl), typically employed for time27# integration in Trixi.jl, do not support this type of stage callback.28#-29# Therefore, subcell limiting with the IDP limiter requires the use of a Trixi-intern30# time integration SSPRK method called with31# ````julia32# Trixi.solve(ode, method(stage_callbacks = stage_callbacks); ...);33# ````34#-35# Right now, only the canonical three-stage, third-order SSPRK method (Shu-Osher)36# [`Trixi.SimpleSSPRK33`](@ref) is implemented.3738# # [IDP Limiting](@id IDPLimiter)39# The implementation of the invariant domain preserving (IDP) limiting approach ([`SubcellLimiterIDP`](@ref))40# is based on [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) and41# [Rueda-Ramírez, Pazner, Gassner (2022)](https://doi.org/10.101/j.compfluid.2022.105627).42# It supports several types of limiting which are enabled by passing parameters individually.4344# ### [Global bounds](@id global_bounds)45# If enabled, the global bounds enforce physical admissibility conditions, such as non-negativity46# of variables. This can be done for conservative variables, where the limiter is of a one-sided47# Zalesak-type ([Zalesak, 1979](https://doi.org/10.1016/0021-9991(79)90051-2)), and general48# non-linear variables, where a Newton-bisection algorithm is used to enforce the bounds.4950# The Newton-bisection algorithm is an iterative method and requires some parameters.51# It uses a fixed maximum number of iteration steps (`max_iterations_newton = 10`) and52# relative/absolute tolerances (`newton_tolerances = (1.0e-12, 1.0e-14)`). The given values are53# sufficient in most cases and therefore used as default. If the implemented bounds checking54# functionality indicates problems with the limiting (see [below](@ref subcell_bounds_check))55# the Newton method with the chosen parameters might not manage to converge. If so, adapting56# the mentioned parameters helps fix that.57# Additionally, there is the parameter58# `gamma_constant_newton`, which can be used to scale the antidiffusive flux for the computation59# of the blending coefficients of nonlinear variables. The default value is `2 * ndims(equations)`,60# as it was shown by [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) [Section 4.2.2.]61# that this value guarantees the fulfillment of bounds for a forward-Euler increment.6263# Very small non-negative values can be an issue as well. That's why we use an additional64# correction factor in the calculation of the global bounds,65# ```math66# u^{new} \geq \beta * u^{FV}.67# ```68# By default, $\beta$ (named `positivity_correction_factor`) is set to `0.1` which works properly69# in most of the tested setups.7071# #### Conservative variables72# The procedure to enforce global bounds for a conservative variables is as follows:73# If you want to guarantee non-negativity for the density of the compressible Euler equations,74# you pass the specific quantity name of the conservative variable.75using Trixi76equations = CompressibleEulerEquations2D(1.4)7778# The quantity name of the density is `rho` which is how we enable its limiting.79positivity_variables_cons = ["rho"]8081# The quantity names are passed as a vector to allow several quantities.82# This is used, for instance, if you want to limit the density of two different components using83# the multicomponent compressible Euler equations.84equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.648),85gas_constants = (0.287, 1.578))8687# Then, we just pass both quantity names.88positivity_variables_cons = ["rho1", "rho2"]8990# Alternatively, it is possible to all limit all density variables with a general command using91positivity_variables_cons = ["rho" * string(i) for i in eachcomponent(equations)]9293# #### Non-linear variables94# To allow limitation for all possible non-linear variables, including variables defined95# on-the-fly, you can directly pass the function that computes the quantity for which you want96# to enforce positivity. For instance, if you want to enforce non-negativity for the pressure,97# do as follows.98positivity_variables_nonlinear = [pressure]99100# ### Local bounds101# Second, Trixi.jl supports the limiting with local bounds for conservative variables using a102# two-sided Zalesak-type limiter ([Zalesak, 1979](https://doi.org/10.1016/0021-9991(79)90051-2))103# and for general non-linear variables using a one-sided Newton-bisection algorithm.104# They allow to avoid spurious oscillations within the global bounds and to improve the105# shock-capturing capabilities of the method. The corresponding numerical admissibility conditions106# are frequently formulated as local maximum or minimum principles. The local bounds are computed107# using the maximum and minimum values of all local neighboring nodes. Within this calculation we108# use the low-order FV solution values for each node.109110# As for the limiting with global bounds you are passing the quantity names of the conservative111# variables you want to limit. So, to limit the density with lower and upper local bounds pass112# the following.113local_twosided_variables_cons = ["rho"]114115# To limit non-linear variables locally, pass the variable function combined with the requested116# bound (`min` or `max`) as a tuple. For instance, to impose a lower local bound on the modified117# specific entropy [`entropy_guermond_etal`](@ref), use118local_onesided_variables_nonlinear = [(entropy_guermond_etal, min)]119120# ## Exemplary simulation121# How to set up a simulation using the IDP limiting becomes clearer when looking at an exemplary122# setup. This will be a simplified version of `tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell.jl`.123# Since the setup is mostly very similar to a pure DGSEM setup as in124# `tree_2d_dgsem/elixir_euler_blast_wave.jl`, the equivalent parts are used without any explanation125# here.126using Trixi127128equations = CompressibleEulerEquations2D(1.4)129130function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D)131## Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave"132## Set up polar coordinates133inicenter = SVector(0.0, 0.0)134x_norm = x[1] - inicenter[1]135y_norm = x[2] - inicenter[2]136r = sqrt(x_norm^2 + y_norm^2)137phi = atan(y_norm, x_norm)138sin_phi, cos_phi = sincos(phi)139140## Calculate primitive variables141rho = r > 0.5 ? 1.0 : 1.1691142v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi143v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi144p = r > 0.5 ? 1.0E-3 : 1.245145146return prim2cons(SVector(rho, v1, v2, p), equations)147end148initial_condition = initial_condition_blast_wave;149150# Since the surface integral is equal for both the DG and the subcell FV method, the limiting is151# applied only in the volume integral.152#-153# Note, that the DG method is based on the flux differencing formulation. Hence, you have to use a154# two-point flux, such as [`flux_ranocha`](@ref), [`flux_shima_etal`](@ref), [`flux_chandrashekar`](@ref)155# or [`flux_kennedy_gruber`](@ref), for the DG volume flux.156surface_flux = flux_lax_friedrichs157volume_flux = flux_ranocha158159# The limiter is implemented within [`SubcellLimiterIDP`](@ref). It always requires the160# parameters `equations` and `basis`. With additional parameters (described [above](@ref IDPLimiter)161# or listed in the docstring) you can specify and enable additional limiting options.162# Here, the simulation should contain local limiting for the density using lower and upper bounds.163basis = LobattoLegendreBasis(3)164limiter_idp = SubcellLimiterIDP(equations, basis;165local_twosided_variables_cons = ["rho"])166167# The initialized limiter is passed to `VolumeIntegralSubcellLimiting` in addition to the volume168# fluxes of the low-order and high-order scheme.169volume_integral = VolumeIntegralSubcellLimiting(limiter_idp;170volume_flux_dg = volume_flux,171volume_flux_fv = surface_flux)172173# Then, the volume integral is passed to `solver` as it is done for the standard flux-differencing174# DG scheme or the element-wise limiting.175solver = DGSEM(basis, surface_flux, volume_integral)176#-177coordinates_min = (-2.0, -2.0)178coordinates_max = (2.0, 2.0)179mesh = TreeMesh(coordinates_min, coordinates_max,180initial_refinement_level = 5,181n_cells_max = 10_000,182periodicity = true)183184semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver;185boundary_conditions = boundary_condition_periodic)186187tspan = (0.0, 2.0)188ode = semidiscretize(semi, tspan)189190summary_callback = SummaryCallback()191192analysis_interval = 1000193analysis_callback = AnalysisCallback(semi, interval = analysis_interval)194195alive_callback = AliveCallback(analysis_interval = analysis_interval)196197save_solution = SaveSolutionCallback(interval = 1000,198save_initial_solution = true,199save_final_solution = true,200solution_variables = cons2prim)201202stepsize_callback = StepsizeCallback(cfl = 0.3)203204callbacks = CallbackSet(summary_callback,205analysis_callback, alive_callback,206save_solution,207stepsize_callback);208209# As explained above, the IDP limiter works a-posteriori and requires the additional use of a210# correction stage implemented with the stage callback [`SubcellLimiterIDPCorrection`](@ref).211# This callback is passed within a tuple to the time integration method.212stage_callbacks = (SubcellLimiterIDPCorrection(),)213214# Moreover, as mentioned before as well, simulations with subcell limiting require a Trixi-intern215# SSPRK time integration methods with passed stage callbacks and a Trixi-intern `Trixi.solve(...)`216# routine.217sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks);218dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback219callback = callbacks);220221# ## Visualization222# As for a standard simulation in Trixi.jl, it is possible to visualize the solution using the223# `plot` routine from Plots.jl.224using Plots225plot(sol)226227# To get an additional look at the amount of limiting that is used, you can use the visualization228# approach using the [`SaveSolutionCallback`](@ref), [`Trixi2Vtk`](https://github.com/trixi-framework/Trixi2Vtk.jl)229# and [ParaView](https://www.paraview.org/download/). More details about this procedure230# can be found in the [visualization documentation](@ref visualization).231#-232# With that implementation and the standard procedure used for Trixi2Vtk you get the following233# dropdown menu in ParaView.234#-235# 236237# The resulting visualization of the density and the limiting parameter then looks like this.238# 239240# You can see that the limiting coefficient does not lie in the interval [0,1] because Trixi2Vtk241# interpolates all quantities to regular nodes by default.242# You can disable this functionality with `reinterpolate=false` within the call of `trixi2vtk(...)`243# and get the following visualization.244# 245246# ## [Bounds checking](@id subcell_bounds_check)247# Subcell limiting is based on the fulfillment of target bounds - either global or local.248# Although the implementation works and has been thoroughly tested, there are some cases where249# these bounds are not met.250# For instance, the deviations could be in machine precision, which is not problematic.251# Larger deviations can be cause by too large time-step sizes (which can be easily fixed by252# reducing the CFL number), specific boundary conditions or source terms. Insufficient parameters253# for the Newton-bisection algorithm can also be a reason when limiting non-linear variables.254# There are described [above](@ref global_bounds).255#-256# In many cases, it is reasonable to monitor the bounds deviations.257# Because of that, Trixi.jl supports a bounds checking routine implemented using the stage258# callback [`BoundsCheckCallback`](@ref). It checks all target bounds for fulfillment259# in every RK stage. If added to the tuple of stage callbacks like260# ````julia261# stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback())262# ````263# and passed to the time integration method, a summary is added to the final console output.264# For the given example, this summary shows that all bounds are met at all times.265# ````266# ────────────────────────────────────────────────────────────────────────────────────────────────────267# Maximum deviation from bounds:268# ────────────────────────────────────────────────────────────────────────────────────────────────────269# rho:270# - lower bound: 0.0271# - upper bound: 0.0272# ────────────────────────────────────────────────────────────────────────────────────────────────────273# ````274275# Moreover, it is also possible to monitor the bounds deviations incurred during the simulations.276# To do that use the parameter `save_errors = true`, such that the instant deviations are written277# to `deviations.txt` in `output_directory` every `interval` time steps.278# ````julia279# BoundsCheckCallback(save_errors = true, output_directory = "out", interval = 100)280# ````281# Then, for the given example the deviations file contains all daviations for the current282# timestep and simulation time.283# ````284# iter, simu_time, rho_min, rho_max285# 100, 0.29103427131404924, 0.0, 0.0286# 200, 0.5980281923063808, 0.0, 0.0287# 300, 0.9520853560765293, 0.0, 0.0288# 400, 1.3630295622683186, 0.0, 0.0289# 500, 1.8344999624013498, 0.0, 0.0290# 532, 1.9974179806990118, 0.0, 0.0291# ````292293294