Path: blob/main/src/solvers/dgsem_tree/indicators_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# this method is directly used when the indicator is constructed as for shock-capturing volume integrals8# and by the dimension-independent method called for AMR9function create_cache(::Type{IndicatorHennemannGassner},10equations::AbstractEquations{3}, basis::LobattoLegendreBasis)11uEltype = real(basis)12alpha = Vector{uEltype}()13alpha_tmp = similar(alpha)1415A3d = Array{uEltype, 3}1617indicator_threaded = A3d[A3d(undef,18nnodes(basis), nnodes(basis), nnodes(basis))19for _ in 1:Threads.maxthreadid()]20modal_threaded = A3d[A3d(undef,21nnodes(basis), nnodes(basis), nnodes(basis))22for _ in 1:Threads.maxthreadid()]23modal_tmp1_threaded = A3d[A3d(undef,24nnodes(basis), nnodes(basis), nnodes(basis))25for _ in 1:Threads.maxthreadid()]26modal_tmp2_threaded = A3d[A3d(undef,27nnodes(basis), nnodes(basis), nnodes(basis))28for _ in 1:Threads.maxthreadid()]2930return (; alpha, alpha_tmp, indicator_threaded, modal_threaded,31modal_tmp1_threaded, modal_tmp2_threaded)32end3334# Use this function barrier and unpack inside to avoid passing closures to Polyester.jl35# with @batch (@threaded).36# Otherwise, @threaded does not work here with Julia ARM on macOS.37# See https://github.com/JuliaSIMD/Polyester.jl/issues/88.38@inline function calc_indicator_hennemann_gassner!(indicator_hg, threshold, parameter_s,39u, element, mesh::AbstractMesh{3},40equations, dg, cache)41@unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg42@unpack alpha, alpha_tmp, indicator_threaded, modal_threaded,43modal_tmp1_threaded, modal_tmp2_threaded = indicator_hg.cache4445indicator = indicator_threaded[Threads.threadid()]46modal = modal_threaded[Threads.threadid()]47modal_tmp1 = modal_tmp1_threaded[Threads.threadid()]48modal_tmp2 = modal_tmp2_threaded[Threads.threadid()]4950# Calculate indicator variables at Gauss-Lobatto nodes51for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)52u_local = get_node_vars(u, equations, dg, i, j, k, element)53indicator[i, j, k] = indicator_hg.variable(u_local, equations)54end5556# Convert to modal representation57multiply_scalar_dimensionwise!(modal, dg.basis.inverse_vandermonde_legendre,58indicator, modal_tmp1, modal_tmp2)5960# Calculate total energies without two highest, without highest, and for all modes61total_energy_clip2 = zero(eltype(modal))62for k in 1:(nnodes(dg) - 2), j in 1:(nnodes(dg) - 2), i in 1:(nnodes(dg) - 2)63total_energy_clip2 += modal[i, j, k]^264end6566total_energy_clip1 = copy(total_energy_clip2)67# Add k = N-1 face: i, j in 1:(N-1)68for j in 1:(nnodes(dg) - 1), i in 1:(nnodes(dg) - 1)69total_energy_clip1 += modal[i, j, nnodes(dg) - 1]^270end71# Add j = N-1 face: i in 1:(N-1), k in 1:(N-2) (k=N-1 already added above)72for k in 1:(nnodes(dg) - 2), i in 1:(nnodes(dg) - 1)73total_energy_clip1 += modal[i, nnodes(dg) - 1, k]^274end75# Add i = N-1 face: j, k in 1:(N-2) (j=N-1 and k=N-1 already added above)76for k in 1:(nnodes(dg) - 2), j in 1:(nnodes(dg) - 2)77total_energy_clip1 += modal[nnodes(dg) - 1, j, k]^278end7980total_energy = copy(total_energy_clip1)81# Add k = N face: i, j in 1:N82for j in 1:nnodes(dg), i in 1:nnodes(dg)83total_energy += modal[i, j, nnodes(dg)]^284end85# Add j = N face: i in 1:N, k in 1:(N-1) (k=N already added above)86for k in 1:(nnodes(dg) - 1), i in 1:nnodes(dg)87total_energy += modal[i, nnodes(dg), k]^288end89# Add i = N face: j, k in 1:(N-1) (j=N and k=N already added above)90for k in 1:(nnodes(dg) - 1), j in 1:(nnodes(dg) - 1)91total_energy += modal[nnodes(dg), j, k]^292end9394# Calculate energy in higher modes95if !(iszero(total_energy))96energy_frac_1 = (total_energy - total_energy_clip1) / total_energy97else98energy_frac_1 = zero(total_energy)99end100if !(iszero(total_energy_clip1))101energy_frac_2 = (total_energy_clip1 - total_energy_clip2) / total_energy_clip1102else103energy_frac_2 = zero(total_energy_clip1)104end105energy = max(energy_frac_1, energy_frac_2)106107alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold)))108109# Take care of the case close to pure DG110if alpha_element < alpha_min111alpha_element = zero(alpha_element)112end113114# Take care of the case close to pure FV115if alpha_element > 1 - alpha_min116alpha_element = one(alpha_element)117end118119# Clip the maximum amount of FV allowed120alpha[element] = min(alpha_max, alpha_element)121return nothing122end123124function apply_smoothing!(mesh::Union{TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}}, alpha,125alpha_tmp, dg,126cache)127128# Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha129# Copy alpha values such that smoothing is indpedenent of the element access order130alpha_tmp .= alpha131132# Loop over interfaces133for interface in eachinterface(dg, cache)134# Get neighboring element ids135left = cache.interfaces.neighbor_ids[1, interface]136right = cache.interfaces.neighbor_ids[2, interface]137138# Apply smoothing139alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left])140alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right])141end142143# Loop over L2 mortars144for mortar in eachmortar(dg, cache)145# Get neighboring element ids146lower_left = cache.mortars.neighbor_ids[1, mortar]147lower_right = cache.mortars.neighbor_ids[2, mortar]148upper_left = cache.mortars.neighbor_ids[3, mortar]149upper_right = cache.mortars.neighbor_ids[4, mortar]150large = cache.mortars.neighbor_ids[5, mortar]151152# Apply smoothing153alpha[lower_left] = max(alpha_tmp[lower_left], 0.5f0 * alpha_tmp[large],154alpha[lower_left])155alpha[lower_right] = max(alpha_tmp[lower_right], 0.5f0 * alpha_tmp[large],156alpha[lower_right])157alpha[upper_left] = max(alpha_tmp[upper_left], 0.5f0 * alpha_tmp[large],158alpha[upper_left])159alpha[upper_right] = max(alpha_tmp[upper_right], 0.5f0 * alpha_tmp[large],160alpha[upper_right])161162alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[lower_left],163alpha[large])164alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[lower_right],165alpha[large])166alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[upper_left],167alpha[large])168alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[upper_right],169alpha[large])170end171172return nothing173end174175# this method is directly used when the indicator is constructed as for shock-capturing volume integrals176# and by the dimension-independent method called for AMR177function create_cache(::Union{Type{IndicatorLöhner}, Type{IndicatorMax}},178equations::AbstractEquations{3}, basis::LobattoLegendreBasis)179uEltype = real(basis)180alpha = Vector{uEltype}()181182A3d = Array{uEltype, 3}183indicator_threaded = A3d[A3d(undef, nnodes(basis), nnodes(basis), nnodes(basis))184for _ in 1:Threads.maxthreadid()]185186return (; alpha, indicator_threaded)187end188189function (löhner::IndicatorLöhner)(u::AbstractArray{<:Any, 5},190mesh, equations, dg::DGSEM, cache;191kwargs...)192@assert nnodes(dg)>=3 "IndicatorLöhner only works for nnodes >= 3 (polydeg > 1)"193@unpack alpha, indicator_threaded = löhner.cache194@unpack variable = löhner195resize!(alpha, nelements(dg, cache))196197@threaded for element in eachelement(dg, cache)198indicator = indicator_threaded[Threads.threadid()]199200# Calculate indicator variables at Gauss-Lobatto nodes201for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)202u_local = get_node_vars(u, equations, dg, i, j, k, element)203indicator[i, j, k] = variable(u_local, equations)204end205206estimate = zero(real(dg))207for k in eachnode(dg), j in eachnode(dg), i in 2:(nnodes(dg) - 1)208# x direction209u0 = indicator[i, j, k]210up = indicator[i + 1, j, k]211um = indicator[i - 1, j, k]212estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner))213end214215for k in eachnode(dg), j in 2:(nnodes(dg) - 1), i in eachnode(dg)216# y direction217u0 = indicator[i, j, k]218up = indicator[i, j + 1, k]219um = indicator[i, j - 1, k]220estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner))221end222223for k in 2:(nnodes(dg) - 1), j in eachnode(dg), i in eachnode(dg)224# y direction225u0 = indicator[i, j, k]226up = indicator[i, j, k + 1]227um = indicator[i, j, k - 1]228estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner))229end230231# use the maximum as DG element indicator232alpha[element] = estimate233end234235return alpha236end237238function (indicator_max::IndicatorMax)(u::AbstractArray{<:Any, 5},239mesh, equations, dg::DGSEM, cache;240kwargs...)241@unpack alpha, indicator_threaded = indicator_max.cache242resize!(alpha, nelements(dg, cache))243indicator_variable = indicator_max.variable244245@threaded for element in eachelement(dg, cache)246indicator = indicator_threaded[Threads.threadid()]247248# Calculate indicator variables at Gauss-Lobatto nodes249for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)250u_local = get_node_vars(u, equations, dg, i, j, k, element)251indicator[i, j, k] = indicator_variable(u_local, equations)252end253254alpha[element] = maximum(indicator)255end256257return alpha258end259260function (indicator::IndicatorNodalFunction)(u::AbstractArray{<:Any, 5},261mesh, equations, dg::DGSEM, cache;262t, kwargs...)263node_coordinates = cache.elements.node_coordinates264@unpack alpha = indicator.cache265resize!(alpha, nelements(dg, cache))266# Extract function to local variable to avoid capturing `indicator` in the threaded loop267indicator_function = indicator.indicator_function268269@threaded for element in eachelement(dg, cache)270estimate = typemin(eltype(alpha))271for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)272u_nodal = get_node_vars(u, equations, dg, i, j, k, element)273x_nodal = get_node_coords(node_coordinates, equations, dg,274i, j, k, element)275estimate = max(estimate,276indicator_function(u_nodal, x_nodal, t))277end278alpha[element] = estimate279end280return alpha281end282end # @muladd283284285