Path: blob/main/src/solvers/dgsem_tree/subcell_limiters_3d.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, 5}, t,16semi, equations)17mesh, _, dg, cache = mesh_equations_solver_cache(semi)1819# Calc bounds inside elements20@threaded for element in eachelement(dg, cache)21# Calculate bounds at Gauss-Lobatto nodes22for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)23var = u[variable, i, j, k, element]24var_min[i, j, k, element] = var25var_max[i, j, k, element] = var26end2728# Apply values in x direction29for k in eachnode(dg), j in eachnode(dg), i in 2:nnodes(dg)30var = u[variable, i - 1, j, k, element]31var_min[i, j, k, element] = min(var_min[i, j, k, element], var)32var_max[i, j, k, element] = max(var_max[i, j, k, element], var)3334var = u[variable, i, j, k, element]35var_min[i - 1, j, k, element] = min(var_min[i - 1, j, k, element], var)36var_max[i - 1, j, k, element] = max(var_max[i - 1, j, k, element], var)37end3839# Apply values in y direction40for k in eachnode(dg), j in 2:nnodes(dg), i in eachnode(dg)41var = u[variable, i, j - 1, k, element]42var_min[i, j, k, element] = min(var_min[i, j, k, element], var)43var_max[i, j, k, element] = max(var_max[i, j, k, element], var)4445var = u[variable, i, j, k, element]46var_min[i, j - 1, k, element] = min(var_min[i, j - 1, k, element], var)47var_max[i, j - 1, k, element] = max(var_max[i, j - 1, k, element], var)48end4950# Apply values in z direction51for k in 2:nnodes(dg), j in eachnode(dg), i in eachnode(dg)52var = u[variable, i, j, k - 1, element]53var_min[i, j, k, element] = min(var_min[i, j, k, element], var)54var_max[i, j, k, element] = max(var_max[i, j, k, element], var)5556var = u[variable, i, j, k, element]57var_min[i, j, k - 1, element] = min(var_min[i, j, k - 1, element], var)58var_max[i, j, k - 1, element] = max(var_max[i, j, k - 1, element], var)59end60end6162# Calc bounds at element interfaces and periodic boundaries63calc_bounds_twosided_interface!(var_min, var_max, variable, u,64semi, mesh, equations)6566# Calc bounds at physical boundaries67(; boundary_conditions) = semi68calc_bounds_twosided_boundary!(var_min, var_max, variable, u, t,69boundary_conditions,70mesh, equations, dg, cache)71return nothing72end7374@inline function calc_bounds_twosided_interface!(var_min, var_max, variable, u,75semi, mesh::TreeMesh3D, equations)76_, _, dg, cache = mesh_equations_solver_cache(semi)7778for interface in eachinterface(dg, cache)79# Get neighboring element ids80left_element = cache.interfaces.neighbor_ids[1, interface]81right_element = cache.interfaces.neighbor_ids[2, interface]8283orientation = cache.interfaces.orientations[interface]8485for j in eachnode(dg), i in eachnode(dg)86# Define node indices for left and right element based on the interface orientation87if orientation == 188# interface in x-direction89index_left = (nnodes(dg), i, j)90index_right = (1, i, j)91elseif orientation == 292# interface in y-direction93index_left = (i, nnodes(dg), j)94index_right = (i, 1, j)95else # if orientation == 396# interface in z-direction97index_left = (i, j, nnodes(dg))98index_right = (i, j, 1)99end100var_left = u[variable, index_left..., left_element]101var_right = u[variable, index_right..., right_element]102103var_min[index_right..., right_element] = min(var_min[index_right...,104right_element],105var_left)106var_max[index_right..., right_element] = max(var_max[index_right...,107right_element],108var_left)109110var_min[index_left..., left_element] = min(var_min[index_left...,111left_element], var_right)112var_max[index_left..., left_element] = max(var_max[index_left...,113left_element], var_right)114end115end116117return nothing118end119120@inline function calc_bounds_twosided_boundary!(var_min, var_max, variable,121u, t, boundary_conditions,122mesh::TreeMesh{3}, equations,123dg, cache)124for boundary in eachboundary(dg, cache)125element = cache.boundaries.neighbor_ids[boundary]126orientation = cache.boundaries.orientations[boundary]127neighbor_side = cache.boundaries.neighbor_sides[boundary]128129for j in eachnode(dg), i in eachnode(dg)130# Define node indices and boundary index based on the orientation and neighbor_side131if neighbor_side == 2 # Element is on the right, boundary on the left132if orientation == 1 # boundary in x-direction133node_index = (1, i, j)134boundary_index = 1135elseif orientation == 2 # boundary in y-direction136node_index = (i, 1, j)137boundary_index = 3138else # orientation == 3 # boundary in z-direction139node_index = (i, j, 1)140boundary_index = 5141end142else # Element is on the left, boundary on the right143if orientation == 1 # boundary in x-direction144node_index = (nnodes(dg), i, j)145boundary_index = 2146elseif orientation == 2 # boundary in y-direction147node_index = (i, nnodes(dg), j)148boundary_index = 4149else # orientation == 3 # boundary in z-direction150node_index = (i, j, nnodes(dg))151boundary_index = 6152end153end154u_inner = get_node_vars(u, equations, dg, node_index..., element)155u_outer = get_boundary_outer_state(u_inner, t,156boundary_conditions[boundary_index],157orientation, boundary_index,158mesh, equations, dg, cache,159node_index..., element)160var_outer = u_outer[variable]161162var_min[node_index..., element] = min(var_min[node_index..., element],163var_outer)164var_max[node_index..., element] = max(var_max[node_index..., element],165var_outer)166end167end168169return nothing170end171172@inline function calc_bounds_onesided!(var_minmax, min_or_max, variable,173u::AbstractArray{<:Any, 5}, t,174semi)175mesh, equations, dg, cache = mesh_equations_solver_cache(semi)176177# The approach used in `calc_bounds_twosided!` is not used here because it requires more178# evaluations of the variable and is therefore slower.179180# Calc bounds inside elements181@threaded for element in eachelement(dg, cache)182# Reset bounds183for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)184if min_or_max === max185var_minmax[i, j, k, element] = typemin(eltype(var_minmax))186else187var_minmax[i, j, k, element] = typemax(eltype(var_minmax))188end189end190191# Calculate bounds at Gauss-Lobatto nodes192for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)193var = variable(get_node_vars(u, equations, dg, i, j, k, element), equations)194var_minmax[i, j, k, element] = min_or_max(var_minmax[i, j, k, element], var)195196if i > 1197var_minmax[i - 1, j, k, element] = min_or_max(var_minmax[i - 1, j, k,198element], var)199end200if i < nnodes(dg)201var_minmax[i + 1, j, k, element] = min_or_max(var_minmax[i + 1, j, k,202element], var)203end204if j > 1205var_minmax[i, j - 1, k, element] = min_or_max(var_minmax[i, j - 1, k,206element], var)207end208if j < nnodes(dg)209var_minmax[i, j + 1, k, element] = min_or_max(var_minmax[i, j + 1, k,210element], var)211end212if k > 1213var_minmax[i, j, k - 1, element] = min_or_max(var_minmax[i, j, k - 1,214element], var)215end216if k < nnodes(dg)217var_minmax[i, j, k + 1, element] = min_or_max(var_minmax[i, j, k + 1,218element], var)219end220end221end222223# Calc bounds at element interfaces and periodic boundaries224calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u,225semi, mesh)226227# Calc bounds at physical boundaries228(; boundary_conditions) = semi229calc_bounds_onesided_boundary!(var_minmax, min_or_max, variable, u, t,230boundary_conditions,231mesh, equations, dg, cache)232233return nothing234end235236@inline function calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u,237semi, mesh::TreeMesh{3})238_, equations, dg, cache = mesh_equations_solver_cache(semi)239240for interface in eachinterface(dg, cache)241# Get neighboring element ids242left_element = cache.interfaces.neighbor_ids[1, interface]243right_element = cache.interfaces.neighbor_ids[2, interface]244245orientation = cache.interfaces.orientations[interface]246247for j in eachnode(dg), i in eachnode(dg)248# Define node indices for left and right element based on the interface orientation249if orientation == 1250# interface in x-direction251index_left = (nnodes(dg), i, j)252index_right = (1, i, j)253elseif orientation == 2254# interface in y-direction255index_left = (i, nnodes(dg), j)256index_right = (i, 1, j)257else # if orientation == 3258# interface in z-direction259index_left = (i, j, nnodes(dg))260index_right = (i, j, 1)261end262var_left = variable(get_node_vars(u, equations, dg, index_left...,263left_element),264equations)265var_right = variable(get_node_vars(u, equations, dg, index_right...,266right_element),267equations)268269var_minmax[index_right..., right_element] = min_or_max(var_minmax[index_right...,270right_element],271var_left)272var_minmax[index_left..., left_element] = min_or_max(var_minmax[index_left...,273left_element],274var_right)275end276end277278return nothing279end280281@inline function calc_bounds_onesided_boundary!(var_minmax, min_or_max, variable,282u, t, boundary_conditions,283mesh::TreeMesh{3}, equations,284dg, cache)285for boundary in eachboundary(dg, cache)286element = cache.boundaries.neighbor_ids[boundary]287orientation = cache.boundaries.orientations[boundary]288neighbor_side = cache.boundaries.neighbor_sides[boundary]289290for j in eachnode(dg), i in eachnode(dg)291# Define node indices and boundary index based on the orientation and neighbor_side292if neighbor_side == 2 # Element is on the right, boundary on the left293if orientation == 1 # boundary in x-direction294node_index = (1, i, j)295boundary_index = 1296elseif orientation == 2 # boundary in y-direction297node_index = (i, 1, j)298boundary_index = 3299else # orientation == 3 # boundary in z-direction300node_index = (i, j, 1)301boundary_index = 5302end303else # Element is on the left, boundary on the right304if orientation == 1 # boundary in x-direction305node_index = (nnodes(dg), i, j)306boundary_index = 2307elseif orientation == 2 # boundary in y-direction308node_index = (i, nnodes(dg), j)309boundary_index = 4310else # orientation == 3 # boundary in z-direction311node_index = (i, j, nnodes(dg))312boundary_index = 6313end314end315u_inner = get_node_vars(u, equations, dg, node_index..., element)316u_outer = get_boundary_outer_state(u_inner, t,317boundary_conditions[boundary_index],318orientation, boundary_index,319mesh, equations, dg, cache,320node_index..., element)321var_outer = variable(u_outer, equations)322323var_minmax[node_index..., element] = min_or_max(var_minmax[node_index...,324element],325var_outer)326end327end328329return nothing330end331332###############################################################################333# Local minimum and maximum limiting of conservative variables334335@inline function idp_local_twosided!(alpha, limiter, u::AbstractArray{<:Any, 5},336t, dt, semi, variable)337mesh, equations, dg, cache = mesh_equations_solver_cache(semi)338(; antidiffusive_flux1_L, antidiffusive_flux1_R, antidiffusive_flux2_L, antidiffusive_flux2_R, antidiffusive_flux3_L, antidiffusive_flux3_R) = cache.antidiffusive_fluxes339(; inverse_weights) = dg.basis340341(; variable_bounds) = limiter.cache.subcell_limiter_coefficients342variable_string = string(variable)343var_min = variable_bounds[Symbol(variable_string, "_min")]344var_max = variable_bounds[Symbol(variable_string, "_max")]345calc_bounds_twosided!(var_min, var_max, variable, u, t, semi, equations)346347@threaded for element in eachelement(dg, semi.cache)348for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)349inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,350mesh, i, j, k, element)351var = u[variable, i, j, k, element]352# Real Zalesak type limiter353# * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids"354# * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics"355# Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is356# for each interface, not each node357358Qp = max(0, (var_max[i, j, k, element] - var) / dt)359Qm = min(0, (var_min[i, j, k, element] - var) / dt)360361# Calculate Pp and Pm362# Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here.363val_flux1_local = inverse_weights[i] *364antidiffusive_flux1_R[variable, i, j, k, element]365val_flux1_local_ip1 = -inverse_weights[i] *366antidiffusive_flux1_L[variable, i + 1, j, k, element]367val_flux2_local = inverse_weights[j] *368antidiffusive_flux2_R[variable, i, j, k, element]369val_flux2_local_jp1 = -inverse_weights[j] *370antidiffusive_flux2_L[variable, i, j + 1, k, element]371val_flux3_local = inverse_weights[k] *372antidiffusive_flux3_R[variable, i, j, k, element]373val_flux3_local_jp1 = -inverse_weights[k] *374antidiffusive_flux3_L[variable, i, j, k + 1, element]375376Pp = max(0, val_flux1_local) + max(0, val_flux1_local_ip1) +377max(0, val_flux2_local) + max(0, val_flux2_local_jp1) +378max(0, val_flux3_local) + max(0, val_flux3_local_jp1)379Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) +380min(0, val_flux2_local) + min(0, val_flux2_local_jp1) +381min(0, val_flux3_local) + min(0, val_flux3_local_jp1)382383Pp = inverse_jacobian * Pp384Pm = inverse_jacobian * Pm385386# Compute blending coefficient avoiding division by zero387# (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8))388Qp = abs(Qp) /389(abs(Pp) + eps(typeof(Qp)) * 100 * abs(var_max[i, j, k, element]))390Qm = abs(Qm) /391(abs(Pm) + eps(typeof(Qm)) * 100 * abs(var_max[i, j, k, element]))392393# Calculate alpha at nodes394alpha[i, j, k, element] = max(alpha[i, j, k, element], 1 - min(1, Qp, Qm))395end396end397398return nothing399end400401##############################################################################402# Local one-sided limiting of nonlinear variables403404@inline function idp_local_onesided!(alpha, limiter, u::AbstractArray{<:Real, 5},405t, dt, semi,406variable, min_or_max)407mesh, equations, dg, cache = mesh_equations_solver_cache(semi)408(; variable_bounds) = limiter.cache.subcell_limiter_coefficients409var_minmax = variable_bounds[Symbol(string(variable), "_", string(min_or_max))]410calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi)411412# Perform Newton's bisection method to find new alpha413@threaded for element in eachelement(dg, cache)414for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)415inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,416mesh, i, j, k, element)417u_local = get_node_vars(u, equations, dg, i, j, k, element)418newton_loops_alpha!(alpha, var_minmax[i, j, k, element],419u_local, i, j, k, element,420variable, min_or_max,421initial_check_local_onesided_newton_idp,422final_check_local_onesided_newton_idp,423inverse_jacobian, dt, equations, dg, cache, limiter)424end425end426427return nothing428end429430###############################################################################431# Global positivity limiting of conservative variables432433@inline function idp_positivity_conservative!(alpha, limiter,434u::AbstractArray{<:Real, 5}, dt, semi,435variable)436mesh, _, dg, cache = mesh_equations_solver_cache(semi)437(; antidiffusive_flux1_L, antidiffusive_flux1_R, antidiffusive_flux2_L, antidiffusive_flux2_R, antidiffusive_flux3_L, antidiffusive_flux3_R) = cache.antidiffusive_fluxes438(; inverse_weights) = dg.basis # Plays role of DG subcell sizes439(; positivity_correction_factor) = limiter440441(; variable_bounds) = limiter.cache.subcell_limiter_coefficients442var_min = variable_bounds[Symbol(string(variable), "_min")]443444@threaded for element in eachelement(dg, semi.cache)445for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)446inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,447mesh, i, j, k, element)448var = u[variable, i, j, k, element]449if var < 0450error("Safe low-order method produces negative value for conservative variable $variable. Try a smaller time step.")451end452453# Compute bound454if limiter.local_twosided &&455(variable in limiter.local_twosided_variables_cons) &&456(var_min[i, j, k, element] >= positivity_correction_factor * var)457# Local limiting is more restrictive that positivity limiting458# => Skip positivity limiting for this node459continue460end461var_min[i, j, k, element] = positivity_correction_factor * var462463# Real one-sided Zalesak-type limiter464# * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids"465# * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics"466# Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is467# for each interface, not each node468Qm = min(0, (var_min[i, j, k, element] - var) / dt)469470# Calculate Pm471# Note: Boundaries of antidiffusive_flux1/2/3 are constant 0, so they make no difference here.472val_flux1_local = inverse_weights[i] *473antidiffusive_flux1_R[variable, i, j, k, element]474val_flux1_local_ip1 = -inverse_weights[i] *475antidiffusive_flux1_L[variable, i + 1, j, k, element]476val_flux2_local = inverse_weights[j] *477antidiffusive_flux2_R[variable, i, j, k, element]478val_flux2_local_jp1 = -inverse_weights[j] *479antidiffusive_flux2_L[variable, i, j + 1, k, element]480val_flux3_local = inverse_weights[k] *481antidiffusive_flux3_R[variable, i, j, k, element]482val_flux3_local_jp1 = -inverse_weights[k] *483antidiffusive_flux3_L[variable, i, j, k + 1, element]484485Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) +486min(0, val_flux2_local) + min(0, val_flux2_local_jp1) +487min(0, val_flux3_local) + min(0, val_flux3_local_jp1)488Pm = inverse_jacobian * Pm489490# Compute blending coefficient avoiding division by zero491# (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8))492Qm = abs(Qm) / (abs(Pm) + eps(typeof(Qm)) * 100)493494# Calculate alpha495alpha[i, j, k, element] = max(alpha[i, j, k, element], 1 - Qm)496end497end498499return nothing500end501502###############################################################################503# Global positivity limiting of nonlinear variables504505@inline function idp_positivity_nonlinear!(alpha, limiter,506u::AbstractArray{<:Real, 5}, dt, semi,507variable)508mesh, equations, dg, cache = mesh_equations_solver_cache(semi)509(; positivity_correction_factor) = limiter510511(; variable_bounds) = limiter.cache.subcell_limiter_coefficients512var_min = variable_bounds[Symbol(string(variable), "_min")]513514@threaded for element in eachelement(dg, semi.cache)515for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)516inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,517mesh, i, j, k, element)518519# Compute bound520u_local = get_node_vars(u, equations, dg, i, j, k, element)521var = variable(u_local, equations)522if var < 0523error("Safe low-order method produces negative value for variable $variable. Try a smaller time step.")524end525var_min[i, j, k, element] = positivity_correction_factor * var526527# Perform Newton's bisection method to find new alpha528newton_loops_alpha!(alpha, var_min[i, j, k, element],529u_local, i, j, k, element,530variable, min,531initial_check_nonnegative_newton_idp,532final_check_nonnegative_newton_idp,533inverse_jacobian, dt, equations, dg, cache, limiter)534end535end536537return nothing538end539540###############################################################################541# Newton-bisection method542543@inline function newton_loops_alpha!(alpha, bound, u, i, j, k, element,544variable, min_or_max,545initial_check, final_check,546inverse_jacobian, dt,547equations::AbstractEquations{3},548dg, cache, limiter)549(; inverse_weights) = dg.basis # Plays role of inverse DG-subcell sizes550(; antidiffusive_flux1_L, antidiffusive_flux1_R, antidiffusive_flux2_L, antidiffusive_flux2_R, antidiffusive_flux3_L, antidiffusive_flux3_R) = cache.antidiffusive_fluxes551552(; gamma_constant_newton) = limiter553554indices = (i, j, k, element)555556# negative xi direction557antidiffusive_flux = gamma_constant_newton * inverse_jacobian *558inverse_weights[i] *559get_node_vars(antidiffusive_flux1_R, equations, dg,560i, j, k, element)561newton_loop!(alpha, bound, u, indices, variable, min_or_max,562initial_check, final_check, equations, dt, limiter, antidiffusive_flux)563564# positive xi direction565antidiffusive_flux = -gamma_constant_newton * inverse_jacobian *566inverse_weights[i] *567get_node_vars(antidiffusive_flux1_L, equations, dg,568i + 1, j, k, element)569newton_loop!(alpha, bound, u, indices, variable, min_or_max,570initial_check, final_check, equations, dt, limiter, antidiffusive_flux)571572# negative eta direction573antidiffusive_flux = gamma_constant_newton * inverse_jacobian *574inverse_weights[j] *575get_node_vars(antidiffusive_flux2_R, equations, dg,576i, j, k, element)577newton_loop!(alpha, bound, u, indices, variable, min_or_max,578initial_check, final_check, equations, dt, limiter, antidiffusive_flux)579580# positive eta direction581antidiffusive_flux = -gamma_constant_newton * inverse_jacobian *582inverse_weights[j] *583get_node_vars(antidiffusive_flux2_L, equations, dg,584i, j + 1, k, element)585newton_loop!(alpha, bound, u, indices, variable, min_or_max,586initial_check, final_check, equations, dt, limiter, antidiffusive_flux)587588# negative zeta direction589antidiffusive_flux = gamma_constant_newton * inverse_jacobian *590inverse_weights[k] *591get_node_vars(antidiffusive_flux3_R, equations, dg,592i, j, k, element)593newton_loop!(alpha, bound, u, indices, variable, min_or_max,594initial_check, final_check, equations, dt, limiter, antidiffusive_flux)595596# positive zeta direction597antidiffusive_flux = -gamma_constant_newton * inverse_jacobian *598inverse_weights[k] *599get_node_vars(antidiffusive_flux3_L, equations, dg,600i, j, k + 1, element)601newton_loop!(alpha, bound, u, indices, variable, min_or_max,602initial_check, final_check, equations, dt, limiter, antidiffusive_flux)603604return nothing605end606end # @muladd607608609