Path: blob/main/src/solvers/dgsem_tree/subcell_limiters.jl
5591 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67abstract type AbstractSubcellLimiter end89function create_cache(typ::Type{LimiterType},10semi) where {LimiterType <: AbstractSubcellLimiter}11return create_cache(typ, mesh_equations_solver_cache(semi)...)12end1314"""15SubcellLimiterIDP(equations::AbstractEquations, basis;16local_twosided_variables_cons = String[],17positivity_variables_cons = String[],18positivity_variables_nonlinear = [],19positivity_correction_factor = 0.1,20local_onesided_variables_nonlinear = [],21max_iterations_newton = 10,22newton_tolerances = (1.0e-12, 1.0e-14),23gamma_constant_newton = 2 * ndims(equations))2425Subcell invariant domain preserving (IDP) limiting used with [`VolumeIntegralSubcellLimiting`](@ref)26including:27- Local two-sided Zalesak-type limiting for conservative variables (`local_twosided_variables_cons`)28- Positivity limiting for conservative variables (`positivity_variables_cons`) and nonlinear variables29(`positivity_variables_nonlinear`)30- Local one-sided limiting for nonlinear variables, e.g. [`entropy_guermond_etal`](@ref) and [`entropy_math`](@ref)31with `local_onesided_variables_nonlinear`3233To use these three limiting options use the following structure:3435***Conservative variables*** to be limited are passed as a vector of strings, e.g.36`local_twosided_variables_cons = ["rho"]` and `positivity_variables_cons = ["rho"]`.37For ***nonlinear variables***, the wanted variable functions are passed within a vector: To ensure38positivity use a plain vector including the desired variables, e.g. `positivity_variables_nonlinear = [pressure]`.39For local one-sided limiting pass the variable function combined with the requested bound40(`min` or `max`) as a tuple. For instance, to impose a lower local bound on the modified specific41entropy by Guermond et al. use `local_onesided_variables_nonlinear = [(entropy_guermond_etal, min)]`.4243The bounds are calculated using the low-order FV solution. The positivity limiter uses44`positivity_correction_factor` such that `u^new >= positivity_correction_factor * u^FV`.45Local and global limiting of nonlinear variables uses a Newton-bisection method with a maximum of46`max_iterations_newton` iterations, relative and absolute tolerances of `newton_tolerances`47and a provisional update constant `gamma_constant_newton` (`gamma_constant_newton>=2*d`,48where `d = #dimensions`). See equation (20) of Pazner (2020) and equation (30) of Rueda-Ramírez et al. (2022).4950!!! note51This limiter and the correction callback [`SubcellLimiterIDPCorrection`](@ref) only work together.52Without the callback, no correction takes place, leading to a standard low-order FV scheme.5354Implementation in 3D:55In 3D, only the positivity limiter for conservative variables using56(`positivity_variables_cons`) is implemented and merged for `P4estMesh`.57`BoundsCheckCallback` is not supported in 3D yet.58More features will follow soon.5960## References6162- Rueda-Ramírez, Pazner, Gassner (2022)63Subcell Limiting Strategies for Discontinuous Galerkin Spectral Element Methods64[DOI: 10.1016/j.compfluid.2022.105627](https://doi.org/10.1016/j.compfluid.2022.105627)65- Pazner (2020)66Sparse invariant domain preserving discontinuous Galerkin methods with subcell convex limiting67[DOI: 10.1016/j.cma.2021.113876](https://doi.org/10.1016/j.cma.2021.113876)68"""69struct SubcellLimiterIDP{RealT <: Real, LimitingVariablesNonlinear,70LimitingOnesidedVariablesNonlinear, Cache} <:71AbstractSubcellLimiter72local_twosided::Bool73local_twosided_variables_cons::Vector{Int} # Local two-sided limiting for conservative variables74positivity::Bool75positivity_variables_cons::Vector{Int} # Positivity for conservative variables76positivity_variables_nonlinear::LimitingVariablesNonlinear # Positivity for nonlinear variables77positivity_correction_factor::RealT78local_onesided::Bool79local_onesided_variables_nonlinear::LimitingOnesidedVariablesNonlinear # Local one-sided limiting for nonlinear variables80cache::Cache81max_iterations_newton::Int82newton_tolerances::Tuple{RealT, RealT} # Relative and absolute tolerances for Newton's method83gamma_constant_newton::RealT # Constant for the subcell limiting of convex (nonlinear) constraints84end8586# this method is used when the limiter is constructed as for shock-capturing volume integrals87function SubcellLimiterIDP(equations::AbstractEquations, basis;88local_twosided_variables_cons = String[],89positivity_variables_cons = String[],90positivity_variables_nonlinear = [],91positivity_correction_factor = 0.1,92local_onesided_variables_nonlinear = [],93max_iterations_newton = 10,94newton_tolerances = (1.0e-12, 1.0e-14),95gamma_constant_newton = 2 * ndims(equations))96local_twosided = (length(local_twosided_variables_cons) > 0)97local_onesided = (length(local_onesided_variables_nonlinear) > 0)98positivity = (length(positivity_variables_cons) +99length(positivity_variables_nonlinear) > 0)100101# When passing `min` or `max` in the elixir, the specific function of Base is used.102# To speed up the simulation, we replace it with `Trixi.min` and `Trixi.max` respectively.103local_onesided_variables_nonlinear_ = Tuple{Function, Function}[]104for (variable, min_or_max) in local_onesided_variables_nonlinear105if min_or_max === Base.max106push!(local_onesided_variables_nonlinear_, (variable, max))107elseif min_or_max === Base.min108push!(local_onesided_variables_nonlinear_, (variable, min))109elseif min_or_max === Trixi.max || min_or_max === Trixi.min110push!(local_onesided_variables_nonlinear_, (variable, min_or_max))111else112error("Parameter $min_or_max is not a valid input. Use `max` or `min` instead.")113end114end115local_onesided_variables_nonlinear_ = Tuple(local_onesided_variables_nonlinear_)116positivity_variables_nonlinear = Tuple(positivity_variables_nonlinear)117118local_twosided_variables_cons_ = get_variable_index.(local_twosided_variables_cons,119equations)120positivity_variables_cons_ = get_variable_index.(positivity_variables_cons,121equations)122123bound_keys = ()124if local_twosided125for v in local_twosided_variables_cons_126v_string = string(v)127bound_keys = (bound_keys..., Symbol(v_string, "_min"),128Symbol(v_string, "_max"))129end130end131if local_onesided132for (variable, min_or_max) in local_onesided_variables_nonlinear_133bound_keys = (bound_keys...,134Symbol(string(variable), "_", string(min_or_max)))135end136end137for v in positivity_variables_cons_138if !(v in local_twosided_variables_cons_)139bound_keys = (bound_keys..., Symbol(string(v), "_min"))140end141end142for variable in positivity_variables_nonlinear143bound_keys = (bound_keys..., Symbol(string(variable), "_min"))144end145146cache = create_cache(SubcellLimiterIDP, equations, basis, bound_keys)147148return SubcellLimiterIDP{typeof(positivity_correction_factor),149typeof(positivity_variables_nonlinear),150typeof(local_onesided_variables_nonlinear_),151typeof(cache)}(local_twosided,152local_twosided_variables_cons_,153positivity, positivity_variables_cons_,154positivity_variables_nonlinear,155positivity_correction_factor,156local_onesided,157local_onesided_variables_nonlinear_,158cache,159max_iterations_newton, newton_tolerances,160gamma_constant_newton)161end162163function Base.show(io::IO, limiter::SubcellLimiterIDP)164@nospecialize limiter # reduce precompilation time165(; local_twosided, positivity, local_onesided) = limiter166167print(io, "SubcellLimiterIDP(")168if !(local_twosided || positivity || local_onesided)169print(io, "No limiter selected => pure DG method")170else171features = String[]172if local_twosided173push!(features, "local min/max")174end175if positivity176push!(features, "positivity")177end178if local_onesided179push!(features, "local onesided")180end181join(io, features, ", ")182print(io, "Limiter=($features), ")183end184print(io, "Local bounds with FV solution")185print(io, ")")186return nothing187end188189function Base.show(io::IO, ::MIME"text/plain", limiter::SubcellLimiterIDP)190@nospecialize limiter # reduce precompilation time191(; local_twosided, positivity, local_onesided) = limiter192193if get(io, :compact, false)194show(io, limiter)195else196if !(local_twosided || positivity || local_onesided)197setup = ["Limiter" => "No limiter selected => pure DG method"]198else199setup = ["Limiter" => ""]200if local_twosided201push!(setup,202"" => "Local two-sided limiting for conservative variables $(limiter.local_twosided_variables_cons)")203end204if positivity205if !isempty(limiter.positivity_variables_cons)206string = "conservative variables $(limiter.positivity_variables_cons)"207push!(setup, "" => "Positivity limiting for " * string)208end209if !isempty(limiter.positivity_variables_nonlinear)210string = "$(limiter.positivity_variables_nonlinear)"211push!(setup, "" => "Positivity limiting for " * string)212end213push!(setup,214"" => "- with positivity correction factor = $(limiter.positivity_correction_factor)")215end216if local_onesided217for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear218push!(setup, "" => "Local $min_or_max limiting for $variable")219end220end221push!(setup, "Local bounds" => "FV solution")222end223summary_box(io, "SubcellLimiterIDP", setup)224end225end226227# this method is used when the limiter is constructed as for shock-capturing volume integrals228function create_cache(limiter::Type{SubcellLimiterIDP},229equations::AbstractEquations{NDIMS},230basis::LobattoLegendreBasis, bound_keys) where {NDIMS}231# The number of elements is not yet known here. So, we initialize the container with 0 elements232# and resize it later while creating the cache for the volume integral.233subcell_limiter_coefficients = Trixi.ContainerSubcellLimiterIDP{NDIMS, real(basis)}(0,234nnodes(basis),235bound_keys)236237# Memory for bounds checking routine with `BoundsCheckCallback`.238# Local variable contains the maximum deviation since the last export.239idp_bounds_delta_local = Dict{Symbol, real(basis)}()240# Global variable contains the total maximum deviation.241idp_bounds_delta_global = Dict{Symbol, real(basis)}()242for key in bound_keys243idp_bounds_delta_local[key] = zero(real(basis))244idp_bounds_delta_global[key] = zero(real(basis))245end246247return (; subcell_limiter_coefficients, idp_bounds_delta_local,248idp_bounds_delta_global)249end250251function resize_subcell_limiter_cache!(limiter::SubcellLimiterIDP, new_size)252resize!(limiter.cache.subcell_limiter_coefficients, new_size)253254return nothing255end256257# While for the element-wise limiting with `VolumeIntegralShockCapturingHG` the indicator is258# called here to get up-to-date values for IO, this is not easily possible in this case259# because the calculation is very integrated into the method.260# See also https://github.com/trixi-framework/Trixi.jl/pull/1611#discussion_r1334553206.261# Therefore, the coefficients at `t=t^{n-1}` are saved. Thus, the coefficients of the first262# stored solution (initial condition) are not yet defined and were manually set to `NaN`.263function get_node_variable(::Val{:limiting_coefficient}, u, mesh, equations, dg, cache)264return dg.volume_integral.limiter.cache.subcell_limiter_coefficients.alpha265end266function get_node_variable(::Val{:limiting_coefficient}, u, mesh, equations, dg, cache,267equations_parabolic, cache_parabolic)268return get_node_variable(Val(:limiting_coefficient), u, mesh, equations, dg, cache)269end270271function (limiter::SubcellLimiterIDP)(u, semi, equations, dg::DGSEM,272t, dt;273kwargs...)274@unpack alpha = limiter.cache.subcell_limiter_coefficients275@trixi_timeit timer() "reset alpha" set_zero!(alpha, dg, semi.cache)276277if limiter.local_twosided278@trixi_timeit timer() "local twosided" idp_local_twosided!(alpha, limiter,279u, t, dt, semi)280end281if limiter.positivity282@trixi_timeit timer() "positivity" idp_positivity!(alpha, limiter, u, dt, semi)283end284if limiter.local_onesided285@trixi_timeit timer() "local onesided" idp_local_onesided!(alpha, limiter,286u, t, dt, semi)287end288289return nothing290end291292###############################################################################293# Local minimum and maximum limiting (conservative variables)294295@inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi)296for variable in limiter.local_twosided_variables_cons297idp_local_twosided!(alpha, limiter, u, t, dt, semi, variable)298end299300return nothing301end302303##############################################################################304# Local minimum or maximum limiting (nonlinear variables)305306@inline function idp_local_onesided!(alpha, limiter, u, t, dt, semi)307for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear308idp_local_onesided!(alpha, limiter, u, t, dt, semi, variable, min_or_max)309end310311return nothing312end313314###############################################################################315# Global positivity limiting (conservative and nonlinear variables)316317@inline function idp_positivity!(alpha, limiter, u, dt, semi)318# Conservative variables319@trixi_timeit timer() "conservative variables" for variable in limiter.positivity_variables_cons320idp_positivity_conservative!(alpha, limiter, u, dt, semi, variable)321end322323# Nonlinear variables324@trixi_timeit timer() "nonlinear variables" for variable in limiter.positivity_variables_nonlinear325idp_positivity_nonlinear!(alpha, limiter, u, dt, semi, variable)326end327328return nothing329end330331###############################################################################332# Newton-bisection method333334@inline function newton_loop!(alpha, bound, u, indices, variable, min_or_max,335initial_check, final_check,336equations, dt, limiter, antidiffusive_flux)337newton_reltol, newton_abstol = limiter.newton_tolerances338339beta = 1 - alpha[indices...]340341beta_L = 0 # alpha = 1342beta_R = beta # No higher beta (lower alpha) than the current one343344u_curr = u + beta * dt * antidiffusive_flux345346# If state is valid, perform initial check and return if correction is not needed347if isvalid(u_curr, equations)348goal = goal_function_newton_idp(variable, bound, u_curr, equations)349350initial_check(min_or_max, bound, goal, newton_abstol) && return nothing351end352353# Newton iterations354for iter in 1:(limiter.max_iterations_newton)355beta_old = beta356357# If the state is valid, evaluate d(goal)/d(beta)358if isvalid(u_curr, equations)359dgoal_dbeta = dgoal_function_newton_idp(variable, u_curr, dt,360antidiffusive_flux, equations)361else # Otherwise, perform a bisection step362dgoal_dbeta = 0363end364365if dgoal_dbeta != 0366# Update beta with Newton's method367beta = beta - goal / dgoal_dbeta368end369370# Check bounds371if (beta < beta_L) || (beta > beta_R) || (dgoal_dbeta == 0) || isnan(beta)372# Out of bounds, do a bisection step373beta = 0.5f0 * (beta_L + beta_R)374# Get new u375u_curr = u + beta * dt * antidiffusive_flux376377# If the state is invalid, finish bisection step without checking tolerance and iterate further378if !isvalid(u_curr, equations)379beta_R = beta380continue381end382383# Check new beta for condition and update bounds384goal = goal_function_newton_idp(variable, bound, u_curr, equations)385if initial_check(min_or_max, bound, goal, newton_abstol)386# New beta fulfills condition387beta_L = beta388else389# New beta does not fulfill condition390beta_R = beta391end392else393# Get new u394u_curr = u + beta * dt * antidiffusive_flux395396# If the state is invalid, redefine right bound without checking tolerance and iterate further397if !isvalid(u_curr, equations)398beta_R = beta399continue400end401402# Evaluate goal function403goal = goal_function_newton_idp(variable, bound, u_curr, equations)404end405406# Check relative tolerance407if abs(beta_old - beta) <= newton_reltol408break409end410411# Check absolute tolerance412if final_check(bound, goal, newton_abstol)413break414end415end416417alpha[indices...] = 1 - beta # new alpha418419return nothing420end421422### Auxiliary routines for Newton's bisection method ###423# Initial checks424@inline function initial_check_local_onesided_newton_idp(::typeof(min), bound,425goal, newton_abstol)426return goal <= max(newton_abstol, abs(bound) * newton_abstol)427end428429@inline function initial_check_local_onesided_newton_idp(::typeof(max), bound,430goal, newton_abstol)431return goal >= -max(newton_abstol, abs(bound) * newton_abstol)432end433434@inline initial_check_nonnegative_newton_idp(min_or_max, bound, goal, newton_abstol) = goal <=4350436437# Goal and d(Goal)/d(u) function438@inline goal_function_newton_idp(variable, bound, u, equations) = bound -439variable(u, equations)440@inline function dgoal_function_newton_idp(variable, u, dt, antidiffusive_flux,441equations)442return -dot(gradient_conservative(variable, u, equations), dt * antidiffusive_flux)443end444445# Final checks446# final check for one-sided local limiting447@inline function final_check_local_onesided_newton_idp(bound, goal, newton_abstol)448return abs(goal) < max(newton_abstol, abs(bound) * newton_abstol)449end450451# final check for nonnegativity limiting452@inline function final_check_nonnegative_newton_idp(bound, goal, newton_abstol)453return (goal <= eps()) && (goal > -max(newton_abstol, abs(bound) * newton_abstol))454end455456###############################################################################457# Auxiliary routine `get_boundary_outer_state` for non-periodic domains458459"""460get_boundary_outer_state(u_inner, t,461boundary_condition::BoundaryConditionDirichlet,462orientation_or_normal, direction,463mesh, equations, dg, cache, indices...)464For subcell limiting, the calculation of local bounds for non-periodic domains requires the boundary465outer state. This function returns the boundary value for [`BoundaryConditionDirichlet`](@ref) at466time `t` and for node with spatial indices `indices` at the boundary with `orientation_or_normal`467and `direction`.468469Should be used together with [`TreeMesh`](@ref) or [`StructuredMesh`](@ref).470471!!! warning "Experimental implementation"472This is an experimental feature and may change in future releases.473"""474@inline function get_boundary_outer_state(u_inner, t,475boundary_condition::BoundaryConditionDirichlet,476orientation_or_normal, direction,477mesh::Union{TreeMesh, StructuredMesh},478equations, dg, cache, indices...)479(; node_coordinates) = cache.elements480481x = get_node_coords(node_coordinates, equations, dg, indices...)482u_outer = boundary_condition.boundary_value_function(x, t, equations)483484return u_outer485end486end # @muladd487488489