Path: blob/main/src/solvers/dgmulti/shock_capturing.jl
5591 views
# Since `@muladd` can fuse multiply-add operations and thus improve performance in1# the flux differencing loops, we opt-in explicitly.2# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.3@muladd begin4#! format: noindent56# by default, return an empty tuple for volume integral caches7function create_cache(mesh::DGMultiMesh{NDIMS}, equations,8volume_integral::VolumeIntegralShockCapturingHGType,9dg::DGMultiFluxDiff{<:GaussSBP}, RealT, uEltype) where {NDIMS}10(; volume_integral_default, volume_integral_blend_high_order) = volume_integral11@assert volume_integral_default isa VolumeIntegralFluxDifferencing "DGMulti is currently only compatible with `VolumeIntegralFluxDifferencing` as `volume_integral_default`"12@assert volume_integral_blend_high_order isa VolumeIntegralFluxDifferencing "DGMulti is currently only compatible with `VolumeIntegralFluxDifferencing` as `volume_integral_blend_high_order`"13# `volume_integral_blend_low_order` limited to finite-volume on Gauss-node subcells1415element_to_element_connectivity = build_element_to_element_connectivity(mesh, dg)1617# create sparse hybridized operators for low order scheme18Qrst, E = StartUpDG.sparse_low_order_SBP_operators(dg.basis)19Brst = map(n -> Diagonal(n .* dg.basis.wf), dg.basis.nrstJ)20sparse_SBP_operators = map((Q, B) -> 0.5f0 * [Q-Q' E'*B; -B*E zeros(size(B))],21Qrst, Brst)2223# Find the joint sparsity pattern of the entire matrix. We store the sparsity pattern as24# an adjoint for faster iteration through the rows.25sparsity_pattern = sum(map(A -> abs.(A)', sparse_SBP_operators)) .>26100 * eps()2728return (; sparse_SBP_operators, sparsity_pattern,29element_to_element_connectivity)30end3132# this method is used when the indicator is constructed as for shock-capturing volume integrals33function create_cache(::Type{IndicatorHennemannGassner}, equations::AbstractEquations,34basis::DGMultiBasis{NDIMS}) where {NDIMS}35uEltype = real(basis)36alpha = Vector{uEltype}()37alpha_tmp = similar(alpha)3839MVec_nodes = MVector{nnodes(basis), uEltype}40indicator_threaded = MVec_nodes[MVec_nodes(undef) for _ in 1:Threads.maxthreadid()]41MVec_modes = MVector{nmodes(basis.N, basis.element_type), uEltype}42modal_threaded = MVec_modes[MVec_modes(undef) for _ in 1:Threads.maxthreadid()]4344inverse_vandermonde = calc_inverse_vandermonde(basis)4546return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, inverse_vandermonde)47end4849# calculates the inverse of the Vandermonde matrix for shock capturing purposes.50# This version is for tensor product elements (Line, Quad, Hex)51function calc_inverse_vandermonde(basis::DGMultiBasis{NDIMS, <:Union{Line, Quad, Hex}}) where {NDIMS}52# initialize inverse Vandermonde matrices at Gauss-Legendre nodes53(; N) = basis54lobatto_node_coordinates_1D, _ = StartUpDG.gauss_lobatto_quad(0, 0, N)55VDM_1D = StartUpDG.vandermonde(Line(), N, lobatto_node_coordinates_1D)56inverse_vandermonde = SimpleKronecker(NDIMS, inv(VDM_1D))5758return inverse_vandermonde59end6061function (indicator_hg::IndicatorHennemannGassner)(u, mesh::DGMultiMesh,62equations,63dg::DGMulti{NDIMS,64<:Union{Line, Quad, Hex}},65cache;66kwargs...) where {NDIMS}67(; alpha_max, alpha_min, alpha_smooth, variable) = indicator_hg68(; alpha, alpha_tmp, indicator_threaded, modal_threaded, inverse_vandermonde) = indicator_hg.cache6970resize!(alpha, nelements(mesh, dg))71if alpha_smooth72resize!(alpha_tmp, nelements(mesh, dg))73end7475# magic parameters76threshold = 0.5f0 * 10^(-1.8 * (dg.basis.N + 1)^0.25)77parameter_s = log((1 - 0.0001) / 0.0001)7879@threaded for element in eachelement(mesh, dg)80indicator = indicator_threaded[Threads.threadid()]81modal_ = modal_threaded[Threads.threadid()]8283# Calculate indicator variable at interpolation (Lobatto) nodes.84# TODO: calculate indicator variables at Gauss nodes or using `cache.entropy_projected_u_values`85for i in eachnode(dg)86indicator[i] = variable(u[i, element], equations)87end8889# multiply by invVDM::SimpleKronecker90LinearAlgebra.mul!(modal_, inverse_vandermonde, indicator)9192# Create Returns functors to return the constructor args (e.g., Base.OneTo(dg.basis.N)) no matter what93# Returns(Base.OneTo(dg.basis.N)) equiv to _ -> Base.OneTo(dg.basis.N), with possibly fewer allocs94return_N_plus_one = Returns(dg.basis.N + 1)95return_to_N_minus_one = Returns(Base.OneTo(dg.basis.N - 1))96return_to_N = Returns(Base.OneTo(dg.basis.N))9798# As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values.99# Thus, Base.ReshapedArray should be used if you are setting values in the array.100# `reshape` is fine if you are only accessing values.101# Here, we reshape modal coefficients to expose the tensor product structure.102103modal = Base.ReshapedArray(modal_, ntuple(return_N_plus_one, NDIMS), ())104105# Calculate total energies for all modes, all modes minus the highest mode, and106# all modes without the two highest modes107total_energy = sum(abs2, modal)108clip_1_ranges = ntuple(return_to_N, NDIMS)109clip_2_ranges = ntuple(return_to_N_minus_one, NDIMS)110# These splattings do not seem to allocate as of Julia 1.9.0?111total_energy_clip1 = sum(abs2, view(modal, clip_1_ranges...))112total_energy_clip2 = sum(abs2, view(modal, clip_2_ranges...))113114# Calculate energy in higher modes115if !(iszero(total_energy))116energy_frac_1 = (total_energy - total_energy_clip1) / total_energy117else118energy_frac_1 = zero(total_energy)119end120if !(iszero(total_energy_clip1))121energy_frac_2 = (total_energy_clip1 - total_energy_clip2) /122total_energy_clip1123else124energy_frac_2 = zero(total_energy_clip1)125end126energy = max(energy_frac_1, energy_frac_2)127128alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold)))129130# Take care of the case close to pure DG131if alpha_element < alpha_min132alpha_element = zero(alpha_element)133end134135# Take care of the case close to pure FV136if alpha_element > 1 - alpha_min137alpha_element = one(alpha_element)138end139140# Clip the maximum amount of FV allowed141alpha[element] = min(alpha_max, alpha_element)142end143144# smooth element indices after they're all computed145if alpha_smooth146apply_smoothing!(mesh, alpha, alpha_tmp, dg, cache)147end148149return alpha150end151152# Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha153function apply_smoothing!(mesh::DGMultiMesh, alpha, alpha_tmp, dg::DGMulti, cache)154155# Copy alpha values such that smoothing is independent of the element access order156alpha_tmp .= alpha157158# smooth alpha with its neighboring value159@threaded for element in eachelement(mesh, dg)160for face in Base.OneTo(StartUpDG.num_faces(dg.basis.element_type))161neighboring_element = cache.element_to_element_connectivity[face, element]162alpha_neighbor = alpha_tmp[neighboring_element]163alpha[element] = max(alpha[element], 0.5f0 * alpha_neighbor)164end165end166167return nothing168end169170function calc_volume_integral!(du, u, mesh::DGMultiMesh,171have_nonconservative_terms, equations,172volume_integral::VolumeIntegralShockCapturingHGType,173dg::DGMultiFluxDiff, cache)174(; indicator, volume_integral_default,175volume_integral_blend_high_order, volume_integral_blend_low_order) = volume_integral176177# Calculate blending factors α: u = u_DG * (1 - α) + u_FV * α178alpha = @trixi_timeit timer() "blending factors" indicator(u, mesh, equations, dg,179cache)180181# For `Float64`, this gives 1.8189894035458565e-12182# For `Float32`, this gives 1.1920929f-5183RealT = eltype(alpha)184atol = max(100 * eps(RealT), eps(RealT)^convert(RealT, 0.75f0))185186@threaded for element in eachelement(mesh, dg)187alpha_element = alpha[element]188# Clip blending factor for values close to zero (-> pure DG)189dg_only = isapprox(alpha_element, 0, atol = atol)190191if dg_only192volume_integral_kernel!(du, u, element, mesh,193have_nonconservative_terms, equations,194volume_integral_default,195dg, cache)196else197# Calculate DG volume integral contribution198volume_integral_kernel!(du, u, element, mesh,199have_nonconservative_terms, equations,200volume_integral_blend_high_order,201dg, cache, 1 - alpha_element)202203# Calculate "FV" low order volume integral contribution204volume_integral_kernel!(du, u, element, mesh,205have_nonconservative_terms, equations,206volume_integral_blend_low_order,207dg, cache, alpha_element)208end209end210211return nothing212end213214function get_sparse_operator_entries(i, j, mesh::DGMultiMesh{1}, cache)215return SVector(cache.sparse_SBP_operators[1][i, j])216end217218function get_sparse_operator_entries(i, j, mesh::DGMultiMesh{2}, cache)219Qr, Qs = cache.sparse_SBP_operators220return SVector(Qr[i, j], Qs[i, j])221end222223function get_sparse_operator_entries(i, j, mesh::DGMultiMesh{3}, cache)224Qr, Qs, Qt = cache.sparse_SBP_operators225return SVector(Qr[i, j], Qs[i, j], Qt[i, j])226end227228function get_contravariant_matrix(element, mesh::DGMultiMesh{1}, cache)229return SMatrix{1, 1}(cache.dxidxhatj[1, 1][1, element])230end231232function get_contravariant_matrix(element, mesh::DGMultiMesh{2, <:Affine}, cache)233(; dxidxhatj) = cache234return SMatrix{2, 2}(dxidxhatj[1, 1][1, element], dxidxhatj[2, 1][1, element],235dxidxhatj[1, 2][1, element], dxidxhatj[2, 2][1, element])236end237238function get_contravariant_matrix(element, mesh::DGMultiMesh{3, <:Affine}, cache)239(; dxidxhatj) = cache240return SMatrix{3, 3}(dxidxhatj[1, 1][1, element], dxidxhatj[2, 1][1, element],241dxidxhatj[3, 1][1, element],242dxidxhatj[1, 2][1, element], dxidxhatj[2, 2][1, element],243dxidxhatj[3, 2][1, element],244dxidxhatj[1, 3][1, element], dxidxhatj[2, 3][1, element],245dxidxhatj[3, 3][1, element])246end247248function get_contravariant_matrix(i, element, mesh::DGMultiMesh{2}, cache)249(; dxidxhatj) = cache250return SMatrix{2, 2}(dxidxhatj[1, 1][i, element], dxidxhatj[2, 1][i, element],251dxidxhatj[1, 2][i, element], dxidxhatj[2, 2][i, element])252end253254function get_contravariant_matrix(i, element, mesh::DGMultiMesh{3}, cache)255(; dxidxhatj) = cache256return SMatrix{3, 3}(dxidxhatj[1, 1][i, element], dxidxhatj[2, 1][i, element],257dxidxhatj[3, 1][i, element],258dxidxhatj[1, 2][i, element], dxidxhatj[2, 2][i, element],259dxidxhatj[3, 2][i, element],260dxidxhatj[1, 3][i, element], dxidxhatj[2, 3][i, element],261dxidxhatj[3, 3][i, element])262end263264function get_avg_contravariant_matrix(i, j, element, mesh::DGMultiMesh, cache)265return 0.5f0 * (get_contravariant_matrix(i, element, mesh, cache) +266get_contravariant_matrix(j, element, mesh, cache))267end268269# On affine meshes, the geometric matrix is constant over the element, so we compute it270# once and reuse it for all node pairs (i, j). The compiler is expected to hoist this271# out of the inner loop after inlining.272@inline function get_low_order_geometric_matrix(i, j, element,273mesh::DGMultiMesh{NDIMS, <:Affine},274cache) where {NDIMS}275return get_contravariant_matrix(element, mesh, cache)276end277278# On non-affine meshes, we use the average of the geometric matrices at nodes i and j279# for provably entropy-stable de-aliasing of the geometric terms.280@inline function get_low_order_geometric_matrix(i, j, element,281mesh::DGMultiMesh,282cache)283return get_avg_contravariant_matrix(i, j, element, mesh, cache)284end285286# Calculates the volume integral corresponding to an algebraic low order method.287# This is used, for example, in shock capturing.288function volume_integral_kernel!(du, u, element, mesh::DGMultiMesh,289have_nonconservative_terms::False, equations,290volume_integral::VolumeIntegralPureLGLFiniteVolume,291dg::DGMultiFluxDiff{<:GaussSBP}, cache,292alpha = true)293(; volume_flux_fv) = volume_integral294295# accumulates output from flux differencing296rhs_local = cache.rhs_local_threaded[Threads.threadid()]297fill!(rhs_local, zero(eltype(rhs_local)))298299u_local = view(cache.entropy_projected_u_values, :, element)300301(; sparsity_pattern) = cache302A_base, row_ids, rows, _ = sparse_operator_data(sparsity_pattern)303for i in row_ids304u_i = u_local[i]305du_i = zero(u_i)306for id in nzrange(A_base, i)307j = rows[id]308u_j = u_local[j]309310# compute (Q_1[i,j], Q_2[i,j], ...) where Q_i = ∑_j dxidxhatj * Q̂_j311geometric_matrix = get_low_order_geometric_matrix(i, j, element,312mesh, cache)313reference_operator_entries = get_sparse_operator_entries(i, j, mesh, cache)314normal_direction_ij = geometric_matrix * reference_operator_entries315316# note that we do not need to normalize `normal_direction_ij` since317# it is typically normalized within the flux computation.318f_ij = volume_flux_fv(u_i, u_j, normal_direction_ij, equations)319du_i = du_i + 2 * f_ij320end321rhs_local[i] = du_i322end323324# TODO: factor this out to avoid calling it twice during calc_volume_integral!325return project_rhs_to_gauss_nodes!(du, rhs_local, element, mesh, dg, cache, alpha)326end327end # @muladd328329330