Path: blob/main/src/solvers/dgsem_tree/subcell_limiters_2d.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: noindent67###############################################################################8# IDP Limiting9###############################################################################1011###############################################################################12# Calculation of local bounds using low-order FV solution1314@inline function calc_bounds_twosided!(var_min, var_max, variable,15u::AbstractArray{<:Any, 4}, t,16semi, equations)17mesh, _, dg, cache = mesh_equations_solver_cache(semi)18# Calc bounds inside elements19@threaded for element in eachelement(dg, cache)20# Calculate bounds at Gauss-Lobatto nodes21for j in eachnode(dg), i in eachnode(dg)22var = u[variable, i, j, element]23var_min[i, j, element] = var24var_max[i, j, element] = var25end2627# Apply values in x direction28for j in eachnode(dg), i in 2:nnodes(dg)29var = u[variable, i - 1, j, element]30var_min[i, j, element] = min(var_min[i, j, element], var)31var_max[i, j, element] = max(var_max[i, j, element], var)3233var = u[variable, i, j, element]34var_min[i - 1, j, element] = min(var_min[i - 1, j, element], var)35var_max[i - 1, j, element] = max(var_max[i - 1, j, element], var)36end3738# Apply values in y direction39for j in 2:nnodes(dg), i in eachnode(dg)40var = u[variable, i, j - 1, element]41var_min[i, j, element] = min(var_min[i, j, element], var)42var_max[i, j, element] = max(var_max[i, j, element], var)4344var = u[variable, i, j, element]45var_min[i, j - 1, element] = min(var_min[i, j - 1, element], var)46var_max[i, j - 1, element] = max(var_max[i, j - 1, element], var)47end48end4950# Calc bounds at element interfaces and periodic boundaries51calc_bounds_twosided_interface!(var_min, var_max, variable, u,52semi, mesh, equations)5354# Calc bounds at physical boundaries55(; boundary_conditions) = semi56calc_bounds_twosided_boundary!(var_min, var_max, variable, u, t,57boundary_conditions,58mesh, equations, dg, cache)59return nothing60end6162@inline function calc_bounds_twosided_interface!(var_min, var_max, variable, u,63semi, mesh::TreeMesh2D, equations)64_, _, dg, cache = mesh_equations_solver_cache(semi)6566for interface in eachinterface(dg, cache)67# Get neighboring element ids68left_element = cache.interfaces.neighbor_ids[1, interface]69right_element = cache.interfaces.neighbor_ids[2, interface]7071orientation = cache.interfaces.orientations[interface]7273for i in eachnode(dg)74# Define node indices for left and right element based on the interface orientation75if orientation == 176index_left = (nnodes(dg), i)77index_right = (1, i)78else # if orientation == 279index_left = (i, nnodes(dg))80index_right = (i, 1)81end82var_left = u[variable, index_left..., left_element]83var_right = u[variable, index_right..., right_element]8485var_min[index_right..., right_element] = min(var_min[index_right...,86right_element],87var_left)88var_max[index_right..., right_element] = max(var_max[index_right...,89right_element],90var_left)9192var_min[index_left..., left_element] = min(var_min[index_left...,93left_element], var_right)94var_max[index_left..., left_element] = max(var_max[index_left...,95left_element], var_right)96end97end9899return nothing100end101102@inline function calc_bounds_twosided_boundary!(var_min, var_max, variable, u, t,103boundary_conditions,104mesh::TreeMesh{2}, equations,105dg, cache)106for boundary in eachboundary(dg, cache)107element = cache.boundaries.neighbor_ids[boundary]108orientation = cache.boundaries.orientations[boundary]109neighbor_side = cache.boundaries.neighbor_sides[boundary]110111for i in eachnode(dg)112if neighbor_side == 2 # Element is on the right, boundary on the left113node_index = (1, i)114boundary_index = 1115else # Element is on the left, boundary on the right116node_index = (nnodes(dg), i)117boundary_index = 2118end119if orientation == 2120node_index = reverse(node_index)121boundary_index += 2122end123u_inner = get_node_vars(u, equations, dg, node_index..., element)124u_outer = get_boundary_outer_state(u_inner, t,125boundary_conditions[boundary_index],126orientation, boundary_index,127mesh, equations, dg, cache,128node_index..., element)129var_outer = u_outer[variable]130131var_min[node_index..., element] = min(var_min[node_index..., element],132var_outer)133var_max[node_index..., element] = max(var_max[node_index..., element],134var_outer)135end136end137138return nothing139end140141@inline function calc_bounds_onesided!(var_minmax, min_or_max, variable,142u::AbstractArray{<:Any, 4}, t,143semi)144mesh, equations, dg, cache = mesh_equations_solver_cache(semi)145146# The approach used in `calc_bounds_twosided!` is not used here because it requires more147# evaluations of the variable and is therefore slower.148149# Calc bounds inside elements150@threaded for element in eachelement(dg, cache)151# Reset bounds152for j in eachnode(dg), i in eachnode(dg)153if min_or_max === max154var_minmax[i, j, element] = typemin(eltype(var_minmax))155else156var_minmax[i, j, element] = typemax(eltype(var_minmax))157end158end159160# Calculate bounds at Gauss-Lobatto nodes161for j in eachnode(dg), i in eachnode(dg)162var = variable(get_node_vars(u, equations, dg, i, j, element), equations)163var_minmax[i, j, element] = min_or_max(var_minmax[i, j, element], var)164165if i > 1166var_minmax[i - 1, j, element] = min_or_max(var_minmax[i - 1, j,167element], var)168end169if i < nnodes(dg)170var_minmax[i + 1, j, element] = min_or_max(var_minmax[i + 1, j,171element], var)172end173if j > 1174var_minmax[i, j - 1, element] = min_or_max(var_minmax[i, j - 1,175element], var)176end177if j < nnodes(dg)178var_minmax[i, j + 1, element] = min_or_max(var_minmax[i, j + 1,179element], var)180end181end182end183184# Calc bounds at element interfaces and periodic boundaries185calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u,186semi, mesh)187188# Calc bounds at physical boundaries189(; boundary_conditions) = semi190calc_bounds_onesided_boundary!(var_minmax, min_or_max, variable, u, t,191boundary_conditions,192mesh, equations, dg, cache)193194return nothing195end196197@inline function calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u,198semi, mesh::TreeMesh2D)199_, equations, dg, cache = mesh_equations_solver_cache(semi)200201for interface in eachinterface(dg, cache)202# Get neighboring element ids203left_element = cache.interfaces.neighbor_ids[1, interface]204right_element = cache.interfaces.neighbor_ids[2, interface]205206orientation = cache.interfaces.orientations[interface]207208for i in eachnode(dg)209# Define node indices for left and right element based on the interface orientation210if orientation == 1211index_left = (nnodes(dg), i)212index_right = (1, i)213else # if orientation == 2214index_left = (i, nnodes(dg))215index_right = (i, 1)216end217var_left = variable(get_node_vars(u, equations, dg, index_left...,218left_element),219equations)220var_right = variable(get_node_vars(u, equations, dg, index_right...,221right_element),222equations)223224var_minmax[index_right..., right_element] = min_or_max(var_minmax[index_right...,225right_element],226var_left)227var_minmax[index_left..., left_element] = min_or_max(var_minmax[index_left...,228left_element],229var_right)230end231end232233return nothing234end235236@inline function calc_bounds_onesided_boundary!(var_minmax, min_or_max, variable, u, t,237boundary_conditions,238mesh::TreeMesh{2}, equations,239dg, cache)240for boundary in eachboundary(dg, cache)241element = cache.boundaries.neighbor_ids[boundary]242orientation = cache.boundaries.orientations[boundary]243neighbor_side = cache.boundaries.neighbor_sides[boundary]244245for i in eachnode(dg)246if neighbor_side == 2 # Element is on the right, boundary on the left247node_index = (1, i)248boundary_index = 1249else # Element is on the left, boundary on the right250node_index = (nnodes(dg), i)251boundary_index = 2252end253if orientation == 2254node_index = reverse(node_index)255boundary_index += 2256end257u_inner = get_node_vars(u, equations, dg, node_index..., element)258u_outer = get_boundary_outer_state(u_inner, t,259boundary_conditions[boundary_index],260orientation, boundary_index,261mesh, equations, dg, cache,262node_index..., element)263var_outer = variable(u_outer, equations)264265var_minmax[node_index..., element] = min_or_max(var_minmax[node_index...,266element],267var_outer)268end269end270271return nothing272end273274###############################################################################275# Local minimum and maximum limiting of conservative variables276277@inline function idp_local_twosided!(alpha, limiter, u::AbstractArray{<:Any, 4},278t, dt, semi, variable)279mesh, equations, dg, cache = mesh_equations_solver_cache(semi)280(; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes281(; inverse_weights) = dg.basis # Plays role of inverse DG-subcell sizes282283(; variable_bounds) = limiter.cache.subcell_limiter_coefficients284variable_string = string(variable)285var_min = variable_bounds[Symbol(variable_string, "_min")]286var_max = variable_bounds[Symbol(variable_string, "_max")]287calc_bounds_twosided!(var_min, var_max, variable, u, t, semi, equations)288289@threaded for element in eachelement(dg, semi.cache)290for j in eachnode(dg), i in eachnode(dg)291inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,292mesh, i, j, element)293var = u[variable, i, j, element]294# Real Zalesak type limiter295# * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids"296# * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics"297# Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is298# for each interface, not each node299300Qp = max(0, (var_max[i, j, element] - var) / dt)301Qm = min(0, (var_min[i, j, element] - var) / dt)302303# Calculate Pp and Pm304# Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here.305val_flux1_local = inverse_weights[i] *306antidiffusive_flux1_R[variable, i, j, element]307val_flux1_local_ip1 = -inverse_weights[i] *308antidiffusive_flux1_L[variable, i + 1, j, element]309val_flux2_local = inverse_weights[j] *310antidiffusive_flux2_R[variable, i, j, element]311val_flux2_local_jp1 = -inverse_weights[j] *312antidiffusive_flux2_L[variable, i, j + 1, element]313314Pp = max(0, val_flux1_local) + max(0, val_flux1_local_ip1) +315max(0, val_flux2_local) + max(0, val_flux2_local_jp1)316Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) +317min(0, val_flux2_local) + min(0, val_flux2_local_jp1)318319Pp = inverse_jacobian * Pp320Pm = inverse_jacobian * Pm321322# Compute blending coefficient avoiding division by zero323# (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8))324Qp = abs(Qp) /325(abs(Pp) + eps(typeof(Qp)) * 100 * abs(var_max[i, j, element]))326Qm = abs(Qm) /327(abs(Pm) + eps(typeof(Qm)) * 100 * abs(var_max[i, j, element]))328329# Calculate alpha at nodes330alpha[i, j, element] = max(alpha[i, j, element], 1 - min(1, Qp, Qm))331end332end333334return nothing335end336337##############################################################################338# Local minimum or maximum limiting of nonlinear variables339340@inline function idp_local_onesided!(alpha, limiter, u::AbstractArray{<:Real, 4},341t, dt, semi, variable, min_or_max)342mesh, equations, dg, cache = mesh_equations_solver_cache(semi)343(; variable_bounds) = limiter.cache.subcell_limiter_coefficients344var_minmax = variable_bounds[Symbol(string(variable), "_", string(min_or_max))]345calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi)346347# Perform Newton's bisection method to find new alpha348@threaded for element in eachelement(dg, cache)349for j in eachnode(dg), i in eachnode(dg)350inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,351mesh, i, j, element)352u_local = get_node_vars(u, equations, dg, i, j, element)353newton_loops_alpha!(alpha, var_minmax[i, j, element], u_local,354i, j, element, variable, min_or_max,355initial_check_local_onesided_newton_idp,356final_check_local_onesided_newton_idp, inverse_jacobian,357dt, equations, dg, cache, limiter)358end359end360361return nothing362end363364###############################################################################365# Global positivity limiting of conservative variables366367@inline function idp_positivity_conservative!(alpha, limiter,368u::AbstractArray{<:Real, 4},369dt, semi, variable)370mesh, _, dg, cache = mesh_equations_solver_cache(semi)371(; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes372(; inverse_weights) = dg.basis373(; positivity_correction_factor) = limiter374375(; variable_bounds) = limiter.cache.subcell_limiter_coefficients376var_min = variable_bounds[Symbol(string(variable), "_min")]377378@threaded for element in eachelement(dg, semi.cache)379for j in eachnode(dg), i in eachnode(dg)380inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,381mesh, i, j, element)382var = u[variable, i, j, element]383if var < 0384error("Safe low-order method produces negative value for conservative variable $variable. Try a smaller time step.")385end386387# Compute bound388if limiter.local_twosided &&389(variable in limiter.local_twosided_variables_cons) &&390(var_min[i, j, element] >= positivity_correction_factor * var)391# Local limiting is more restrictive that positivity limiting392# => Skip positivity limiting for this node393continue394end395var_min[i, j, element] = positivity_correction_factor * var396397# Real one-sided Zalesak-type limiter398# * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids"399# * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics"400# Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is401# for each interface, not each node402Qm = min(0, (var_min[i, j, element] - var) / dt)403404# Calculate Pm405# Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here.406val_flux1_local = inverse_weights[i] *407antidiffusive_flux1_R[variable, i, j, element]408val_flux1_local_ip1 = -inverse_weights[i] *409antidiffusive_flux1_L[variable, i + 1, j, element]410val_flux2_local = inverse_weights[j] *411antidiffusive_flux2_R[variable, i, j, element]412val_flux2_local_jp1 = -inverse_weights[j] *413antidiffusive_flux2_L[variable, i, j + 1, element]414415Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) +416min(0, val_flux2_local) + min(0, val_flux2_local_jp1)417Pm = inverse_jacobian * Pm418419# Compute blending coefficient avoiding division by zero420# (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8))421Qm = abs(Qm) / (abs(Pm) + eps(typeof(Qm)) * 100)422423# Calculate alpha424alpha[i, j, element] = max(alpha[i, j, element], 1 - Qm)425end426end427428return nothing429end430431###############################################################################432# Global positivity limiting of nonlinear variables433434@inline function idp_positivity_nonlinear!(alpha, limiter,435u::AbstractArray{<:Real, 4},436dt, semi, variable)437mesh, equations, dg, cache = mesh_equations_solver_cache(semi)438(; positivity_correction_factor) = limiter439440(; variable_bounds) = limiter.cache.subcell_limiter_coefficients441var_min = variable_bounds[Symbol(string(variable), "_min")]442443@threaded for element in eachelement(dg, semi.cache)444for j in eachnode(dg), i in eachnode(dg)445inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,446mesh, i, j, element)447448# Compute bound449u_local = get_node_vars(u, equations, dg, i, j, element)450var = variable(u_local, equations)451if var < 0452error("Safe low-order method produces negative value for variable $variable. Try a smaller time step.")453end454var_min[i, j, element] = positivity_correction_factor * var455456# Perform Newton's bisection method to find new alpha457newton_loops_alpha!(alpha, var_min[i, j, element], u_local, i, j, element,458variable, min, initial_check_nonnegative_newton_idp,459final_check_nonnegative_newton_idp, inverse_jacobian,460dt, equations, dg, cache, limiter)461end462end463464return nothing465end466467###############################################################################468# Newton-bisection method469470@inline function newton_loops_alpha!(alpha, bound, u, i, j, element,471variable, min_or_max,472initial_check, final_check,473inverse_jacobian, dt,474equations::AbstractEquations{2},475dg, cache, limiter)476(; inverse_weights) = dg.basis # Plays role of inverse DG-subcell sizes477(; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes478479(; gamma_constant_newton) = limiter480481indices = (i, j, element)482483# negative xi direction484antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[i] *485get_node_vars(antidiffusive_flux1_R, equations, dg,486i, j, element)487newton_loop!(alpha, bound, u, indices, variable, min_or_max, initial_check,488final_check, equations, dt, limiter, antidiffusive_flux)489490# positive xi direction491antidiffusive_flux = -gamma_constant_newton * inverse_jacobian *492inverse_weights[i] *493get_node_vars(antidiffusive_flux1_L, equations, dg,494i + 1, j, element)495newton_loop!(alpha, bound, u, indices, variable, min_or_max, initial_check,496final_check, equations, dt, limiter, antidiffusive_flux)497498# negative eta direction499antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[j] *500get_node_vars(antidiffusive_flux2_R, equations, dg,501i, j, element)502newton_loop!(alpha, bound, u, indices, variable, min_or_max, initial_check,503final_check, equations, dt, limiter, antidiffusive_flux)504505# positive eta direction506antidiffusive_flux = -gamma_constant_newton * inverse_jacobian *507inverse_weights[j] *508get_node_vars(antidiffusive_flux2_L, equations, dg,509i, j + 1, element)510newton_loop!(alpha, bound, u, indices, variable, min_or_max, initial_check,511final_check, equations, dt, limiter, antidiffusive_flux)512513return nothing514end515end # @muladd516517518