Path: blob/main/src/solvers/dgsem_tree/dg_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# everything related to a DG semidiscretization in 2D,8# currently limited to Lobatto-Legendre nodes910# This method is called when a SemidiscretizationHyperbolic is constructed.11# It constructs the basic `cache` used throughout the simulation to compute12# the RHS etc.13function create_cache(mesh::Union{TreeMesh{2}, TreeMesh{3}}, equations,14dg::DG, RealT, uEltype)15# Get cells for which an element needs to be created (i.e. all leaf cells)16leaf_cell_ids = local_leaf_cells(mesh.tree)1718elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype)1920interfaces = init_interfaces(leaf_cell_ids, mesh, elements)2122boundaries = init_boundaries(leaf_cell_ids, mesh, elements, dg.basis)2324mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar)2526# Container cache27cache = (; elements, interfaces, boundaries, mortars)2829# Add Volume-Integral cache30cache = (; cache...,31create_cache(mesh, equations, dg.volume_integral, dg, cache, uEltype)...)32# Add Mortar cache33cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...)3435return cache36end3738function create_f_threaded(mesh::AbstractMesh{2}, equations,39dg::DG, uEltype)40A3d = Array{uEltype, 3}4142f1_L_threaded = A3d[A3d(undef, nvariables(equations),43nnodes(dg) + 1, nnodes(dg))44for _ in 1:Threads.maxthreadid()]45f1_R_threaded = A3d[A3d(undef, nvariables(equations),46nnodes(dg) + 1, nnodes(dg))47for _ in 1:Threads.maxthreadid()]48f2_L_threaded = A3d[A3d(undef, nvariables(equations),49nnodes(dg), nnodes(dg) + 1)50for _ in 1:Threads.maxthreadid()]51f2_R_threaded = A3d[A3d(undef, nvariables(equations),52nnodes(dg), nnodes(dg) + 1)53for _ in 1:Threads.maxthreadid()]5455@threaded for t in eachindex(f1_L_threaded)56f1_L_threaded[t][:, 1, :] .= zero(uEltype)57f1_R_threaded[t][:, 1, :] .= zero(uEltype)58f1_L_threaded[t][:, nnodes(dg) + 1, :] .= zero(uEltype)59f1_R_threaded[t][:, nnodes(dg) + 1, :] .= zero(uEltype)6061f2_L_threaded[t][:, :, 1] .= zero(uEltype)62f2_R_threaded[t][:, :, 1] .= zero(uEltype)63f2_L_threaded[t][:, :, nnodes(dg) + 1] .= zero(uEltype)64f2_R_threaded[t][:, :, nnodes(dg) + 1] .= zero(uEltype)65end6667return f1_L_threaded, f1_R_threaded,68f2_L_threaded, f2_R_threaded69end7071function create_cache(mesh::TreeMesh{2}, equations,72volume_integral::AbstractVolumeIntegralSubcell,73dg::DG, cache_containers, uEltype)74fstar1_L_threaded, fstar1_R_threaded,75fstar2_L_threaded, fstar2_R_threaded = create_f_threaded(mesh, equations, dg,76uEltype)7778cache_subcell_limiting = create_cache_subcell_limiting(mesh, equations,79volume_integral, dg,80cache_containers, uEltype)8182return (; fstar1_L_threaded, fstar1_R_threaded,83fstar2_L_threaded, fstar2_R_threaded,84cache_subcell_limiting...)85end8687# The methods below are specialized on the mortar type88# and called from the basic `create_cache` method at the top.89function create_cache(mesh::TreeMesh{2}, equations,90mortar_l2::LobattoLegendreMortarL2, uEltype)91MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)},92uEltype, 2,93nvariables(equations) * nnodes(mortar_l2)}94fstar_primary_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]95fstar_primary_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]96fstar_secondary_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]97fstar_secondary_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]9899cache = (; fstar_primary_upper_threaded, fstar_primary_lower_threaded,100fstar_secondary_upper_threaded, fstar_secondary_lower_threaded)101102return cache103end104105# TODO: Taal discuss/refactor timer, allowing users to pass a custom timer?106107# This function is valid for all non-conforming mesh types, i.e.,108# all meshes that do involve mortar operations.109# Thus, we can use it for the serial (i.e., non-distributed memory parallelized)110# 2D/3D `TreeMesh`es, `P4estMesh`es, and `T8codeMesh`es.111function rhs!(du, u, t,112mesh::Union{TreeMesh{2}, P4estMesh{2}, P4estMeshView{2}, T8codeMesh{2},113TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}},114equations,115boundary_conditions, source_terms::Source,116dg::DG, cache) where {Source}117backend = trixi_backend(u)118119# Reset du120@trixi_timeit_ext backend timer() "reset ∂u/∂t" begin121set_zero!(du, dg, cache)122end123124# Calculate volume integral125@trixi_timeit_ext backend timer() "volume integral" begin126calc_volume_integral!(backend, du, u, mesh,127have_nonconservative_terms(equations), equations,128dg.volume_integral, dg, cache)129end130131# Prolong solution to interfaces132@trixi_timeit_ext backend timer() "prolong2interfaces" begin133prolong2interfaces!(backend, cache, u, mesh, equations, dg)134end135136# Calculate interface fluxes137@trixi_timeit_ext backend timer() "interface flux" begin138calc_interface_flux!(backend, cache.elements.surface_flux_values, mesh,139have_nonconservative_terms(equations), equations,140dg.surface_integral, dg, cache)141end142143# Prolong solution to boundaries144@trixi_timeit_ext backend timer() "prolong2boundaries" begin145prolong2boundaries!(cache, u, mesh, equations, dg)146end147148# Calculate boundary fluxes149@trixi_timeit_ext backend timer() "boundary flux" begin150calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations,151dg.surface_integral, dg)152end153154# Prolong solution to mortars155@trixi_timeit_ext backend timer() "prolong2mortars" begin156prolong2mortars!(cache, u, mesh, equations,157dg.mortar, dg)158end159160# Calculate mortar fluxes161@trixi_timeit_ext backend timer() "mortar flux" begin162calc_mortar_flux!(cache.elements.surface_flux_values, mesh,163have_nonconservative_terms(equations), equations,164dg.mortar, dg.surface_integral, dg, cache)165end166167# Calculate surface integrals168@trixi_timeit_ext backend timer() "surface integral" begin169calc_surface_integral!(backend, du, u, mesh, equations,170dg.surface_integral, dg, cache)171end172173# Apply Jacobian from mapping to reference element174@trixi_timeit_ext backend timer() "Jacobian" begin175apply_jacobian!(backend, du, mesh, equations, dg, cache)176end177178# Calculate source terms179@trixi_timeit_ext backend timer() "source terms" begin180calc_sources!(du, u, t, source_terms, equations, dg, cache)181end182183return nothing184end185186#=187`weak_form_kernel!` is only implemented for conserved terms as188non-conservative terms should always be discretized in conjunction with a flux-splitting scheme,189see `flux_differencing_kernel!`.190This treatment is required to achieve, e.g., entropy-stability or well-balancedness.191See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064192=#193@inline function weak_form_kernel!(du, u,194element, ::Type{<:TreeMesh{2}},195have_nonconservative_terms::False, equations,196dg::DGSEM, cache, alpha = true)197# true * [some floating point value] == [exactly the same floating point value]198# This can (hopefully) be optimized away due to constant propagation.199@unpack derivative_hat = dg.basis200201# Calculate volume terms in one element202for j in eachnode(dg), i in eachnode(dg)203u_node = get_node_vars(u, equations, dg, i, j, element)204205flux1 = flux(u_node, 1, equations)206for ii in eachnode(dg)207multiply_add_to_node_vars!(du, alpha * derivative_hat[ii, i], flux1,208equations, dg, ii, j, element)209end210211flux2 = flux(u_node, 2, equations)212for jj in eachnode(dg)213multiply_add_to_node_vars!(du, alpha * derivative_hat[jj, j], flux2,214equations, dg, i, jj, element)215end216end217218return nothing219end220221@inline function flux_differencing_kernel!(du, u, element, ::Type{<:TreeMesh{2}},222have_nonconservative_terms::False, equations,223volume_flux, dg::DGSEM, cache, alpha = true)224# true * [some floating point value] == [exactly the same floating point value]225# This can (hopefully) be optimized away due to constant propagation.226@unpack derivative_split = dg.basis227228# Calculate volume integral in one element229for j in eachnode(dg), i in eachnode(dg)230u_node = get_node_vars(u, equations, dg, i, j, element)231232# All diagonal entries of `derivative_split` are zero. Thus, we can skip233# the computation of the diagonal terms. In addition, we use the symmetry234# of the `volume_flux` to save half of the possible two-point flux235# computations.236237# x direction238for ii in (i + 1):nnodes(dg)239u_node_ii = get_node_vars(u, equations, dg, ii, j, element)240flux1 = volume_flux(u_node, u_node_ii, 1, equations)241multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], flux1,242equations, dg, i, j, element)243multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], flux1,244equations, dg, ii, j, element)245end246247# y direction248for jj in (j + 1):nnodes(dg)249u_node_jj = get_node_vars(u, equations, dg, i, jj, element)250flux2 = volume_flux(u_node, u_node_jj, 2, equations)251multiply_add_to_node_vars!(du, alpha * derivative_split[j, jj], flux2,252equations, dg, i, j, element)253multiply_add_to_node_vars!(du, alpha * derivative_split[jj, j], flux2,254equations, dg, i, jj, element)255end256end257end258259@inline function flux_differencing_kernel!(du, u, element, MeshT::Type{<:TreeMesh{2}},260have_nonconservative_terms::True, equations,261volume_flux, dg::DGSEM, cache, alpha = true)262# true * [some floating point value] == [exactly the same floating point value]263# This can (hopefully) be optimized away due to constant propagation.264@unpack derivative_split = dg.basis265symmetric_flux, nonconservative_flux = volume_flux266267# Apply the symmetric flux as usual268flux_differencing_kernel!(du, u, element, MeshT, False(), equations, symmetric_flux,269dg, cache, alpha)270271# Calculate the remaining volume terms using the nonsymmetric generalized flux272for j in eachnode(dg), i in eachnode(dg)273u_node = get_node_vars(u, equations, dg, i, j, element)274275# The diagonal terms are zero since the diagonal of `derivative_split`276# is zero. We ignore this for now.277278# x direction279integral_contribution = zero(u_node)280for ii in eachnode(dg)281u_node_ii = get_node_vars(u, equations, dg, ii, j, element)282noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations)283integral_contribution = integral_contribution +284derivative_split[i, ii] * noncons_flux1285end286287# y direction288for jj in eachnode(dg)289u_node_jj = get_node_vars(u, equations, dg, i, jj, element)290noncons_flux2 = nonconservative_flux(u_node, u_node_jj, 2, equations)291integral_contribution = integral_contribution +292derivative_split[j, jj] * noncons_flux2293end294295# The factor 0.5 cancels the factor 2 in the flux differencing form296multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations,297dg, i, j, element)298end299end300301@inline function fvO2_kernel!(du, u,302MeshT::Type{<:Union{TreeMesh{2}, StructuredMesh{2},303UnstructuredMesh2D, P4estMesh{2},304T8codeMesh{2}}},305have_nonconservative_terms, equations,306volume_flux_fv, dg::DGSEM, cache, element,307sc_interface_coords, reconstruction_mode, slope_limiter,308cons2recon, recon2cons,309alpha = true)310@unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache311@unpack inverse_weights = dg.basis # Plays role of inverse DG-subcell sizes312313# Calculate FV two-point fluxes314fstar1_L = fstar1_L_threaded[Threads.threadid()]315fstar2_L = fstar2_L_threaded[Threads.threadid()]316fstar1_R = fstar1_R_threaded[Threads.threadid()]317fstar2_R = fstar2_R_threaded[Threads.threadid()]318calcflux_fvO2!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, MeshT,319have_nonconservative_terms, equations,320volume_flux_fv, dg, element, cache,321sc_interface_coords, reconstruction_mode, slope_limiter,322cons2recon, recon2cons)323324# Calculate FV volume integral contribution325for j in eachnode(dg), i in eachnode(dg)326for v in eachvariable(equations)327du[v, i, j, element] += (alpha *328(inverse_weights[i] *329(fstar1_L[v, i + 1, j] - fstar1_R[v, i, j]) +330inverse_weights[j] *331(fstar2_L[v, i, j + 1] - fstar2_R[v, i, j])))332end333end334335return nothing336end337338@inline function calcflux_fvO2!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u,339::Type{<:TreeMesh{2}},340have_nonconservative_terms::False,341equations, volume_flux_fv, dg::DGSEM, element, cache,342sc_interface_coords, reconstruction_mode, slope_limiter,343cons2recon, recon2cons)344for j in eachnode(dg), i in 2:nnodes(dg)345# We compute FV02 fluxes at the (nnodes(dg) - 1) subcell boundaries346# See `calcflux_fvO2!` in dg_1d.jl for a schematic of how it works347348# The left subcell node values are labelled `_ll` (left-left) and `_lr` (left-right), while349# the right subcell node values are labelled `_rl` (right-left) and `_rr` (right-right).350351## Obtain unlimited values in reconstruction variables ##352353# Note: If i - 2 = 0 we do not go to neighbor element, as one would do in a finite volume scheme.354# Here, we keep it purely cell-local, thus overshoots between elements are not strictly ruled out,355# **unless** `reconstruction_mode` is set to `reconstruction_O2_inner`356u_ll = cons2recon(get_node_vars(u, equations, dg, max(1, i - 2), j, element),357equations)358u_lr = cons2recon(get_node_vars(u, equations, dg, i - 1, j, element),359equations)360u_rl = cons2recon(get_node_vars(u, equations, dg, i, j, element),361equations)362# Note: If i + 1 > nnodes(dg) we do not go to neighbor element, as one would do in a finite volume scheme.363# Here, we keep it purely cell-local, thus overshoots between elements are not strictly ruled out,364# **unless** `reconstruction_mode` is set to `reconstruction_O2_inner`365u_rr = cons2recon(get_node_vars(u, equations, dg, min(nnodes(dg), i + 1), j,366element), equations)367368## Reconstruct values at interfaces with limiting ##369u_l, u_r = reconstruction_mode(u_ll, u_lr, u_rl, u_rr,370sc_interface_coords, i,371slope_limiter, dg)372373## Convert reconstruction variables back to conservative variables ##374flux = volume_flux_fv(recon2cons(u_l, equations), recon2cons(u_r, equations),3751, equations) # orientation 1: x direction376377set_node_vars!(fstar1_L, flux, equations, dg, i, j)378set_node_vars!(fstar1_R, flux, equations, dg, i, j)379end380381for j in 2:nnodes(dg), i in eachnode(dg)382u_ll = cons2recon(get_node_vars(u, equations, dg, i, max(1, j - 2), element),383equations)384u_lr = cons2recon(get_node_vars(u, equations, dg, i, j - 1, element),385equations)386u_rl = cons2recon(get_node_vars(u, equations, dg, i, j, element),387equations)388u_rr = cons2recon(get_node_vars(u, equations, dg, i, min(nnodes(dg), j + 1),389element), equations)390391u_l, u_r = reconstruction_mode(u_ll, u_lr, u_rl, u_rr,392sc_interface_coords, j,393slope_limiter, dg)394395flux = volume_flux_fv(recon2cons(u_l, equations), recon2cons(u_r, equations),3962, equations) # orientation 2: y direction397398set_node_vars!(fstar2_L, flux, equations, dg, i, j)399set_node_vars!(fstar2_R, flux, equations, dg, i, j)400end401402return nothing403end404405@inline function fv_kernel!(du, u,406MeshT::Type{<:Union{TreeMesh{2}, StructuredMesh{2},407UnstructuredMesh2D, P4estMesh{2},408T8codeMesh{2}}},409have_nonconservative_terms, equations,410volume_flux_fv, dg::DGSEM, cache, element, alpha = true)411@unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache412@unpack inverse_weights = dg.basis # Plays role of inverse DG-subcell sizes413414# Calculate FV two-point fluxes415fstar1_L = fstar1_L_threaded[Threads.threadid()]416fstar2_L = fstar2_L_threaded[Threads.threadid()]417fstar1_R = fstar1_R_threaded[Threads.threadid()]418fstar2_R = fstar2_R_threaded[Threads.threadid()]419calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, MeshT,420have_nonconservative_terms, equations, volume_flux_fv, dg, element,421cache)422423# Calculate FV volume integral contribution424for j in eachnode(dg), i in eachnode(dg)425for v in eachvariable(equations)426du[v, i, j, element] += (alpha *427(inverse_weights[i] *428(fstar1_L[v, i + 1, j] - fstar1_R[v, i, j]) +429inverse_weights[j] *430(fstar2_L[v, i, j + 1] - fstar2_R[v, i, j])))431end432end433434return nothing435end436437# Compute the normal flux for the FV method on cartesian subcells, see438# Hennemann, Rueda-Ramírez, Hindenlang, Gassner (2020)439# "A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations"440# [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044)441@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u,442::Type{<:TreeMesh{2}},443have_nonconservative_terms::False, equations,444volume_flux_fv, dg::DGSEM, element, cache)445for j in eachnode(dg), i in 2:nnodes(dg)446u_ll = get_node_vars(u, equations, dg, i - 1, j, element)447u_rr = get_node_vars(u, equations, dg, i, j, element)448flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction449set_node_vars!(fstar1_L, flux, equations, dg, i, j)450set_node_vars!(fstar1_R, flux, equations, dg, i, j)451end452453for j in 2:nnodes(dg), i in eachnode(dg)454u_ll = get_node_vars(u, equations, dg, i, j - 1, element)455u_rr = get_node_vars(u, equations, dg, i, j, element)456flux = volume_flux_fv(u_ll, u_rr, 2, equations) # orientation 2: y direction457set_node_vars!(fstar2_L, flux, equations, dg, i, j)458set_node_vars!(fstar2_R, flux, equations, dg, i, j)459end460461return nothing462end463464@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u,465::Type{<:TreeMesh{2}},466have_nonconservative_terms::True, equations,467volume_flux_fv, dg::DGSEM, element, cache)468volume_flux, nonconservative_flux = volume_flux_fv469470# Fluxes in x-direction471for j in eachnode(dg), i in 2:nnodes(dg)472u_ll = get_node_vars(u, equations, dg, i - 1, j, element)473u_rr = get_node_vars(u, equations, dg, i, j, element)474475# Compute conservative part476f1 = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction477478# Compute nonconservative part479# Note the factor 0.5 necessary for the nonconservative fluxes based on480# the interpretation of global SBP operators coupled discontinuously via481# central fluxes/SATs482f1_L = f1 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations)483f1_R = f1 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations)484485# Copy to temporary storage486set_node_vars!(fstar1_L, f1_L, equations, dg, i, j)487set_node_vars!(fstar1_R, f1_R, equations, dg, i, j)488end489490# Fluxes in y-direction491for j in 2:nnodes(dg), i in eachnode(dg)492u_ll = get_node_vars(u, equations, dg, i, j - 1, element)493u_rr = get_node_vars(u, equations, dg, i, j, element)494495# Compute conservative part496f2 = volume_flux(u_ll, u_rr, 2, equations) # orientation 2: y direction497498# Compute nonconservative part499# Note the factor 0.5 necessary for the nonconservative fluxes based on500# the interpretation of global SBP operators coupled discontinuously via501# central fluxes/SATs502f2_L = f2 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 2, equations)503f2_R = f2 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 2, equations)504505# Copy to temporary storage506set_node_vars!(fstar2_L, f2_L, equations, dg, i, j)507set_node_vars!(fstar2_R, f2_R, equations, dg, i, j)508end509510return nothing511end512513function prolong2interfaces!(backend::Nothing, cache, u, mesh::TreeMesh{2}, equations,514dg::DG)515@unpack interfaces = cache516@unpack orientations, neighbor_ids = interfaces517interfaces_u = interfaces.u518519@threaded for interface in eachinterface(dg, cache)520left_element = neighbor_ids[1, interface]521right_element = neighbor_ids[2, interface]522523if orientations[interface] == 1524# interface in x-direction525for j in eachnode(dg), v in eachvariable(equations)526interfaces_u[1, v, j, interface] = u[v, nnodes(dg), j, left_element]527interfaces_u[2, v, j, interface] = u[v, 1, j, right_element]528end529else # if orientations[interface] == 2530# interface in y-direction531for i in eachnode(dg), v in eachvariable(equations)532interfaces_u[1, v, i, interface] = u[v, i, nnodes(dg), left_element]533interfaces_u[2, v, i, interface] = u[v, i, 1, right_element]534end535end536end537538return nothing539end540541function prolong2interfaces!(backend::Nothing, cache, u, mesh::TreeMesh{2}, equations,542dg::DGSEM{<:GaussLegendreBasis})543@unpack interfaces = cache544@unpack orientations, neighbor_ids = interfaces545@unpack boundary_interpolation = dg.basis546interfaces_u = interfaces.u547548@threaded for interface in eachinterface(dg, cache)549left_element = neighbor_ids[1, interface]550right_element = neighbor_ids[2, interface]551552if orientations[interface] == 1553# interface in x-direction554for j in eachnode(dg)555for v in eachvariable(equations)556# Interpolate to the interfaces using a local variable for557# the accumulation of values (to reduce global memory operations).558interface_u_1 = zero(eltype(interfaces_u))559interface_u_2 = zero(eltype(interfaces_u))560for ii in eachnode(dg)561# Not += to allow `@muladd` to turn these into FMAs562# (see comment at the top of the file)563interface_u_1 = (interface_u_1 +564u[v, ii, j, left_element] *565boundary_interpolation[ii, 2])566interface_u_2 = (interface_u_2 +567u[v, ii, j, right_element] *568boundary_interpolation[ii, 1])569end570interfaces_u[1, v, j, interface] = interface_u_1571interfaces_u[2, v, j, interface] = interface_u_2572end573end574else # if orientations[interface] == 2575# interface in y-direction576for i in eachnode(dg)577for v in eachvariable(equations)578interface_u_1 = zero(eltype(interfaces_u))579interface_u_2 = zero(eltype(interfaces_u))580for jj in eachnode(dg)581interface_u_1 = (interface_u_1 +582u[v, i, jj, left_element] *583boundary_interpolation[jj, 2])584interface_u_2 = (interface_u_2 +585u[v, i, jj, right_element] *586boundary_interpolation[jj, 1])587end588interfaces_u[1, v, i, interface] = interface_u_1589interfaces_u[2, v, i, interface] = interface_u_2590end591end592end593end594595return nothing596end597598function calc_interface_flux!(backend::Nothing, surface_flux_values,599mesh::TreeMesh{2},600have_nonconservative_terms::False, equations,601surface_integral, dg::DG, cache)602@unpack surface_flux = surface_integral603@unpack u, neighbor_ids, orientations = cache.interfaces604605@threaded for interface in eachinterface(dg, cache)606# Get neighboring elements607left_id = neighbor_ids[1, interface]608right_id = neighbor_ids[2, interface]609610# Determine interface direction with respect to elements:611# orientation = 1: left -> 2, right -> 1612# orientation = 2: left -> 4, right -> 3613left_direction = 2 * orientations[interface]614right_direction = 2 * orientations[interface] - 1615616for i in eachnode(dg)617# Call pointwise Riemann solver618u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface)619flux = surface_flux(u_ll, u_rr, orientations[interface], equations)620621# Copy flux to left and right element storage622for v in eachvariable(equations)623surface_flux_values[v, i, left_direction, left_id] = flux[v]624surface_flux_values[v, i, right_direction, right_id] = flux[v]625end626end627end628629return nothing630end631632function calc_interface_flux!(backend::Nothing, surface_flux_values,633mesh::TreeMesh{2},634have_nonconservative_terms::True, equations,635surface_integral, dg::DG, cache)636surface_flux, nonconservative_flux = surface_integral.surface_flux637@unpack u, neighbor_ids, orientations = cache.interfaces638639@threaded for interface in eachinterface(dg, cache)640# Get neighboring elements641left_id = neighbor_ids[1, interface]642right_id = neighbor_ids[2, interface]643644# Determine interface direction with respect to elements:645# orientation = 1: left -> 2, right -> 1646# orientation = 2: left -> 4, right -> 3647left_direction = 2 * orientations[interface]648right_direction = 2 * orientations[interface] - 1649650for i in eachnode(dg)651# Call pointwise Riemann solver652orientation = orientations[interface]653u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface)654flux = surface_flux(u_ll, u_rr, orientation, equations)655656# Compute both nonconservative fluxes657noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations)658noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations)659660# Copy flux to left and right element storage661for v in eachvariable(equations)662# Note the factor 0.5 necessary for the nonconservative fluxes based on663# the interpretation of global SBP operators coupled discontinuously via664# central fluxes/SATs665surface_flux_values[v, i, left_direction, left_id] = flux[v] +6660.5f0 *667noncons_left[v]668surface_flux_values[v, i, right_direction, right_id] = flux[v] +6690.5f0 *670noncons_right[v]671end672end673end674675return nothing676end677678function prolong2boundaries!(cache, u,679mesh::TreeMesh{2}, equations, dg::DG)680@unpack boundaries = cache681@unpack orientations, neighbor_sides = boundaries682683@threaded for boundary in eachboundary(dg, cache)684element = boundaries.neighbor_ids[boundary]685686if orientations[boundary] == 1687# boundary in x-direction688if neighbor_sides[boundary] == 1689# element in -x direction of boundary690for l in eachnode(dg), v in eachvariable(equations)691boundaries.u[1, v, l, boundary] = u[v, nnodes(dg), l, element]692end693else # Element in +x direction of boundary694for l in eachnode(dg), v in eachvariable(equations)695boundaries.u[2, v, l, boundary] = u[v, 1, l, element]696end697end698else # if orientations[boundary] == 2699# boundary in y-direction700if neighbor_sides[boundary] == 1701# element in -y direction of boundary702for l in eachnode(dg), v in eachvariable(equations)703boundaries.u[1, v, l, boundary] = u[v, l, nnodes(dg), element]704end705else706# element in +y direction of boundary707for l in eachnode(dg), v in eachvariable(equations)708boundaries.u[2, v, l, boundary] = u[v, l, 1, element]709end710end711end712end713714return nothing715end716717function prolong2boundaries!(cache, u,718mesh::TreeMesh{2}, equations,719dg::DGSEM{<:GaussLegendreBasis})720@unpack boundaries = cache721@unpack orientations, neighbor_sides = boundaries722@unpack boundary_interpolation = dg.basis723724@threaded for boundary in eachboundary(dg, cache)725element = boundaries.neighbor_ids[boundary]726727if orientations[boundary] == 1728# boundary in x-direction729if neighbor_sides[boundary] == 1730# element in -x direction of boundary => interpolate to right boundary node (+1)731for l in eachnode(dg)732for v in eachvariable(equations)733# Interpolate to the boundaries using a local variable for734# the accumulation of values (to reduce global memory operations).735boundary_u = zero(eltype(boundaries.u))736for ii in eachnode(dg)737# Not += to allow `@muladd` to turn these into FMAs738# (see comment at the top of the file)739boundary_u = (boundary_u +740u[v, ii, l, element] *741boundary_interpolation[ii, 2])742end743boundaries.u[1, v, l, boundary] = boundary_u744end745end746else # element in +x direction of boundary => interpolate to left boundary node (-1)747for l in eachnode(dg)748for v in eachvariable(equations)749boundary_u = zero(eltype(boundaries.u))750for ii in eachnode(dg)751boundary_u = (boundary_u +752u[v, ii, l, element] *753boundary_interpolation[ii, 1])754end755boundaries.u[2, v, l, boundary] = boundary_u756end757end758end759else # if orientations[boundary] == 2760# boundary in y-direction761if neighbor_sides[boundary] == 1762# element in -y direction of boundary => interpolate to right boundary node (+1)763for l in eachnode(dg)764for v in eachvariable(equations)765boundary_u = zero(eltype(boundaries.u))766for jj in eachnode(dg)767boundary_u = (boundary_u +768u[v, l, jj, element] *769boundary_interpolation[jj, 2])770end771boundaries.u[1, v, l, boundary] = boundary_u772end773end774else # element in +y direction of boundary => interpolate to left boundary node (-1)775for l in eachnode(dg)776for v in eachvariable(equations)777boundary_u = zero(eltype(boundaries.u))778for jj in eachnode(dg)779boundary_u = (boundary_u +780u[v, l, jj, element] *781boundary_interpolation[jj, 1])782end783boundaries.u[2, v, l, boundary] = boundary_u784end785end786end787end788end789790return nothing791end792793function calc_boundary_flux!(cache, t, boundary_conditions::NamedTuple,794mesh::TreeMesh{2}, equations, surface_integral, dg::DG)795@unpack surface_flux_values = cache.elements796@unpack n_boundaries_per_direction = cache.boundaries797798# Calculate indices799lasts = accumulate(+, n_boundaries_per_direction)800firsts = lasts - n_boundaries_per_direction .+ 1801802# Calc boundary fluxes in each direction803calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[1],804have_nonconservative_terms(equations),805equations, surface_integral, dg, cache,8061, firsts[1], lasts[1])807calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[2],808have_nonconservative_terms(equations),809equations, surface_integral, dg, cache,8102, firsts[2], lasts[2])811calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[3],812have_nonconservative_terms(equations),813equations, surface_integral, dg, cache,8143, firsts[3], lasts[3])815calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[4],816have_nonconservative_terms(equations),817equations, surface_integral, dg, cache,8184, firsts[4], lasts[4])819820return nothing821end822823function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 4},824t,825boundary_condition,826have_nonconservative_terms::False, equations,827surface_integral, dg::DG, cache,828direction, first_boundary, last_boundary)829@unpack surface_flux = surface_integral830@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries831832@threaded for boundary in first_boundary:last_boundary833# Get neighboring element834neighbor = neighbor_ids[boundary]835836for i in eachnode(dg)837# Get boundary flux838u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, boundary)839if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right840u_inner = u_ll841else # Element is on the right, boundary on the left842u_inner = u_rr843end844x = get_node_coords(node_coordinates, equations, dg, i, boundary)845flux = boundary_condition(u_inner, orientations[boundary], direction, x, t,846surface_flux,847equations)848849# Copy flux to left and right element storage850for v in eachvariable(equations)851surface_flux_values[v, i, direction, neighbor] = flux[v]852end853end854end855856return nothing857end858859function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 4},860t,861boundary_condition,862have_nonconservative_terms::True, equations,863surface_integral, dg::DG, cache,864direction, first_boundary, last_boundary)865@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries866867@threaded for boundary in first_boundary:last_boundary868# Get neighboring element869neighbor = neighbor_ids[boundary]870871for i in eachnode(dg)872# Get boundary flux873u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, boundary)874if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right875u_inner = u_ll876else # Element is on the right, boundary on the left877u_inner = u_rr878end879x = get_node_coords(node_coordinates, equations, dg, i, boundary)880flux, noncons_flux = boundary_condition(u_inner, orientations[boundary],881direction, x, t,882surface_integral.surface_flux,883equations)884885# Copy flux to left and right element storage886for v in eachvariable(equations)887surface_flux_values[v, i, direction, neighbor] = flux[v] +8880.5f0 * noncons_flux[v]889end890end891end892893return nothing894end895896function prolong2mortars!(cache, u,897mesh::TreeMesh{2}, equations,898mortar_l2::LobattoLegendreMortarL2,899dg::DGSEM)900@threaded for mortar in eachmortar(dg, cache)901large_element = cache.mortars.neighbor_ids[3, mortar]902upper_element = cache.mortars.neighbor_ids[2, mortar]903lower_element = cache.mortars.neighbor_ids[1, mortar]904905# Copy solution small to small906if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side907if cache.mortars.orientations[mortar] == 1908# L2 mortars in x-direction909for l in eachnode(dg)910for v in eachvariable(equations)911cache.mortars.u_upper[2, v, l, mortar] = u[v, 1, l,912upper_element]913cache.mortars.u_lower[2, v, l, mortar] = u[v, 1, l,914lower_element]915end916end917else918# L2 mortars in y-direction919for l in eachnode(dg)920for v in eachvariable(equations)921cache.mortars.u_upper[2, v, l, mortar] = u[v, l, 1,922upper_element]923cache.mortars.u_lower[2, v, l, mortar] = u[v, l, 1,924lower_element]925end926end927end928else # large_sides[mortar] == 2 -> small elements on left side929if cache.mortars.orientations[mortar] == 1930# L2 mortars in x-direction931for l in eachnode(dg)932for v in eachvariable(equations)933cache.mortars.u_upper[1, v, l, mortar] = u[v, nnodes(dg), l,934upper_element]935cache.mortars.u_lower[1, v, l, mortar] = u[v, nnodes(dg), l,936lower_element]937end938end939else940# L2 mortars in y-direction941for l in eachnode(dg)942for v in eachvariable(equations)943cache.mortars.u_upper[1, v, l, mortar] = u[v, l, nnodes(dg),944upper_element]945cache.mortars.u_lower[1, v, l, mortar] = u[v, l, nnodes(dg),946lower_element]947end948end949end950end951952# Interpolate large element face data to small interface locations953if cache.mortars.large_sides[mortar] == 1 # -> large element on left side954leftright = 1955if cache.mortars.orientations[mortar] == 1956# L2 mortars in x-direction957u_large = view(u, :, nnodes(dg), :, large_element)958element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,959mortar, u_large)960else961# L2 mortars in y-direction962u_large = view(u, :, :, nnodes(dg), large_element)963element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,964mortar, u_large)965end966else # large_sides[mortar] == 2 -> large element on right side967leftright = 2968if cache.mortars.orientations[mortar] == 1969# L2 mortars in x-direction970u_large = view(u, :, 1, :, large_element)971element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,972mortar, u_large)973else974# L2 mortars in y-direction975u_large = view(u, :, :, 1, large_element)976element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,977mortar, u_large)978end979end980end981982return nothing983end984985@inline function element_solutions_to_mortars!(mortars,986mortar_l2::LobattoLegendreMortarL2,987leftright, mortar,988u_large::AbstractArray{<:Any, 2})989multiply_dimensionwise!(view(mortars.u_upper, leftright, :, :, mortar),990mortar_l2.forward_upper, u_large)991multiply_dimensionwise!(view(mortars.u_lower, leftright, :, :, mortar),992mortar_l2.forward_lower, u_large)993return nothing994end995996function calc_mortar_flux!(surface_flux_values,997mesh::TreeMesh{2},998have_nonconservative_terms::False, equations,999mortar_l2::LobattoLegendreMortarL2,1000surface_integral, dg::DG, cache)1001@unpack surface_flux = surface_integral1002@unpack u_lower, u_upper, orientations = cache.mortars1003@unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded,1004fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache10051006@threaded for mortar in eachmortar(dg, cache)1007# Choose thread-specific pre-allocated container1008fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()]1009fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()]1010fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()]1011fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()]10121013# Calculate fluxes1014orientation = orientations[mortar]1015calc_fstar!(fstar_primary_upper, equations, surface_flux, dg, u_upper, mortar,1016orientation)1017calc_fstar!(fstar_primary_lower, equations, surface_flux, dg, u_lower, mortar,1018orientation)1019calc_fstar!(fstar_secondary_upper, equations, surface_flux, dg, u_upper, mortar,1020orientation)1021calc_fstar!(fstar_secondary_lower, equations, surface_flux, dg, u_lower, mortar,1022orientation)10231024mortar_fluxes_to_elements!(surface_flux_values,1025mesh, equations, mortar_l2, dg, cache,1026mortar, fstar_primary_upper, fstar_primary_lower,1027fstar_secondary_upper, fstar_secondary_lower)1028end10291030return nothing1031end10321033function calc_mortar_flux!(surface_flux_values,1034mesh::TreeMesh{2},1035have_nonconservative_terms::True, equations,1036mortar_l2::LobattoLegendreMortarL2,1037surface_integral, dg::DG, cache)1038surface_flux, nonconservative_flux = surface_integral.surface_flux1039@unpack u_lower, u_upper, orientations, large_sides = cache.mortars1040@unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded,1041fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache10421043@threaded for mortar in eachmortar(dg, cache)1044# Choose thread-specific pre-allocated container1045fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()]1046fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()]1047fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()]1048fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()]10491050# Calculate fluxes1051orientation = orientations[mortar]1052calc_fstar!(fstar_primary_upper, equations, surface_flux, dg, u_upper, mortar,1053orientation)1054calc_fstar!(fstar_primary_lower, equations, surface_flux, dg, u_lower, mortar,1055orientation)1056calc_fstar!(fstar_secondary_upper, equations, surface_flux, dg, u_upper, mortar,1057orientation)1058calc_fstar!(fstar_secondary_lower, equations, surface_flux, dg, u_lower, mortar,1059orientation)10601061# Add nonconservative fluxes.1062# These need to be adapted on the geometry (left/right) since the order of1063# the arguments matters, based on the global SBP operator interpretation.1064# The same interpretation (global SBP operators coupled discontinuously via1065# central fluxes/SATs) explains why we need the factor 0.5.1066# Alternatively, you can also follow the argumentation of Bohm et al. 20181067# ("nonconservative diamond flux")1068if large_sides[mortar] == 1 # -> small elements on right side1069for i in eachnode(dg)1070# Pull the left and right solutions1071u_upper_ll, u_upper_rr = get_surface_node_vars(u_upper, equations, dg,1072i, mortar)1073u_lower_ll, u_lower_rr = get_surface_node_vars(u_lower, equations, dg,1074i, mortar)1075# Call pointwise nonconservative term1076noncons_primary_upper = nonconservative_flux(u_upper_ll, u_upper_rr,1077orientation, equations)1078noncons_primary_lower = nonconservative_flux(u_lower_ll, u_lower_rr,1079orientation, equations)1080noncons_secondary_upper = nonconservative_flux(u_upper_rr, u_upper_ll,1081orientation, equations)1082noncons_secondary_lower = nonconservative_flux(u_lower_rr, u_lower_ll,1083orientation, equations)1084# Add to primary and secondary temporary storage1085multiply_add_to_node_vars!(fstar_primary_upper, 0.5f0,1086noncons_primary_upper, equations,1087dg, i)1088multiply_add_to_node_vars!(fstar_primary_lower, 0.5f0,1089noncons_primary_lower, equations,1090dg, i)1091multiply_add_to_node_vars!(fstar_secondary_upper, 0.5f0,1092noncons_secondary_upper, equations,1093dg, i)1094multiply_add_to_node_vars!(fstar_secondary_lower, 0.5f0,1095noncons_secondary_lower, equations,1096dg, i)1097end1098else # large_sides[mortar] == 2 -> small elements on the left1099for i in eachnode(dg)1100# Pull the left and right solutions1101u_upper_ll, u_upper_rr = get_surface_node_vars(u_upper, equations, dg,1102i, mortar)1103u_lower_ll, u_lower_rr = get_surface_node_vars(u_lower, equations, dg,1104i, mortar)1105# Call pointwise nonconservative term1106noncons_primary_upper = nonconservative_flux(u_upper_rr, u_upper_ll,1107orientation, equations)1108noncons_primary_lower = nonconservative_flux(u_lower_rr, u_lower_ll,1109orientation, equations)1110noncons_secondary_upper = nonconservative_flux(u_upper_ll, u_upper_rr,1111orientation, equations)1112noncons_secondary_lower = nonconservative_flux(u_lower_ll, u_lower_rr,1113orientation, equations)1114# Add to primary and secondary temporary storage1115multiply_add_to_node_vars!(fstar_primary_upper, 0.5f0,1116noncons_primary_upper, equations,1117dg, i)1118multiply_add_to_node_vars!(fstar_primary_lower, 0.5f0,1119noncons_primary_lower, equations,1120dg, i)1121multiply_add_to_node_vars!(fstar_secondary_upper, 0.5f0,1122noncons_secondary_upper, equations,1123dg, i)1124multiply_add_to_node_vars!(fstar_secondary_lower, 0.5f0,1125noncons_secondary_lower, equations,1126dg, i)1127end1128end11291130mortar_fluxes_to_elements!(surface_flux_values,1131mesh, equations, mortar_l2, dg, cache,1132mortar, fstar_primary_upper, fstar_primary_lower,1133fstar_secondary_upper, fstar_secondary_lower)1134end11351136return nothing1137end11381139# For Gauss-Legendre DGSEM mortars are not yet implemented1140function calc_mortar_flux!(surface_flux_values,1141mesh::TreeMesh{2},1142have_nonconservative_terms, equations,1143mortar::Nothing, surface_integral,1144dg::DGSEM{<:GaussLegendreBasis}, cache)1145@assert isempty(eachmortar(dg, cache))1146return nothing1147end11481149@inline function calc_fstar!(destination::AbstractArray{<:Any, 2}, equations,1150surface_flux, dg::DGSEM,1151u_interfaces, interface, orientation)1152for i in eachnode(dg)1153# Call pointwise two-point numerical flux function1154u_ll, u_rr = get_surface_node_vars(u_interfaces, equations, dg, i, interface)1155flux = surface_flux(u_ll, u_rr, orientation, equations)11561157# Copy flux to left and right element storage1158set_node_vars!(destination, flux, equations, dg, i)1159end11601161return nothing1162end11631164@inline function mortar_fluxes_to_elements!(surface_flux_values,1165mesh::TreeMesh{2}, equations,1166mortar_l2::LobattoLegendreMortarL2,1167dg::DGSEM, cache,1168mortar, fstar_primary_upper,1169fstar_primary_lower,1170fstar_secondary_upper,1171fstar_secondary_lower)1172large_element = cache.mortars.neighbor_ids[3, mortar]1173upper_element = cache.mortars.neighbor_ids[2, mortar]1174lower_element = cache.mortars.neighbor_ids[1, mortar]11751176# Copy flux small to small1177if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side1178if cache.mortars.orientations[mortar] == 11179# L2 mortars in x-direction1180direction = 11181else1182# L2 mortars in y-direction1183direction = 31184end1185else # large_sides[mortar] == 2 -> small elements on left side1186if cache.mortars.orientations[mortar] == 11187# L2 mortars in x-direction1188direction = 21189else1190# L2 mortars in y-direction1191direction = 41192end1193end1194surface_flux_values[:, :, direction, upper_element] .= fstar_primary_upper1195surface_flux_values[:, :, direction, lower_element] .= fstar_primary_lower11961197# Project small fluxes to large element1198if cache.mortars.large_sides[mortar] == 1 # -> large element on left side1199if cache.mortars.orientations[mortar] == 11200# L2 mortars in x-direction1201direction = 21202else1203# L2 mortars in y-direction1204direction = 41205end1206else # large_sides[mortar] == 2 -> large element on right side1207if cache.mortars.orientations[mortar] == 11208# L2 mortars in x-direction1209direction = 11210else1211# L2 mortars in y-direction1212direction = 31213end1214end12151216# TODO: Taal performance1217# for v in eachvariable(equations)1218# # The code below is semantically equivalent to1219# # surface_flux_values[v, :, direction, large_element] .=1220# # (mortar_l2.reverse_upper * fstar_upper[v, :] + mortar_l2.reverse_lower * fstar_lower[v, :])1221# # but faster and does not allocate.1222# # Note that `true * some_float == some_float` in Julia, i.e. `true` acts as1223# # a universal `one`. Hence, the second `mul!` means "add the matrix-vector1224# # product to the current value of the destination".1225# @views mul!(surface_flux_values[v, :, direction, large_element],1226# mortar_l2.reverse_upper, fstar_upper[v, :])1227# @views mul!(surface_flux_values[v, :, direction, large_element],1228# mortar_l2.reverse_lower, fstar_lower[v, :], true, true)1229# end1230# The code above could be replaced by the following code. However, the relative efficiency1231# depends on the types of fstar_upper/fstar_lower and dg.l2mortar_reverse_upper.1232# Using StaticArrays for both makes the code above faster for common test cases.1233multiply_dimensionwise!(view(surface_flux_values, :, :, direction, large_element),1234mortar_l2.reverse_upper, fstar_secondary_upper,1235mortar_l2.reverse_lower, fstar_secondary_lower)12361237return nothing1238end12391240function calc_surface_integral!(backend::Nothing, du, u,1241mesh::Union{TreeMesh{2}, StructuredMesh{2},1242StructuredMeshView{2}},1243equations, surface_integral::SurfaceIntegralWeakForm,1244dg::DG, cache)1245@unpack inverse_weights = dg.basis1246@unpack surface_flux_values = cache.elements12471248# This computes the **negative** surface integral contribution,1249# i.e., M^{-1} * boundary_interpolation^T (which is for Gauss-Lobatto DGSEM just M^{-1} * B)1250# and the missing "-" is taken care of by `apply_jacobian!`.1251#1252# We also use explicit assignments instead of `+=` to let `@muladd` turn these1253# into FMAs (see comment at the top of the file).1254factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±11255@threaded for element in eachelement(dg, cache)1256for l in eachnode(dg)1257for v in eachvariable(equations)1258# surface at -x1259du[v, 1, l, element] = (du[v, 1, l, element] -1260surface_flux_values[v, l, 1, element] *1261factor)12621263# surface at +x1264du[v, nnodes(dg), l, element] = (du[v, nnodes(dg), l, element] +1265surface_flux_values[v, l, 2, element] *1266factor)12671268# surface at -y1269du[v, l, 1, element] = (du[v, l, 1, element] -1270surface_flux_values[v, l, 3, element] *1271factor)12721273# surface at +y1274du[v, l, nnodes(dg), element] = (du[v, l, nnodes(dg), element] +1275surface_flux_values[v, l, 4, element] *1276factor)1277end1278end1279end12801281return nothing1282end12831284function calc_surface_integral!(backend::Nothing, du, u,1285mesh::Union{TreeMesh{2},1286StructuredMesh{2}, StructuredMeshView{2}},1287equations, surface_integral::SurfaceIntegralWeakForm,1288dg::DGSEM{<:GaussLegendreBasis}, cache)1289@unpack boundary_interpolation_inverse_weights = dg.basis1290@unpack surface_flux_values = cache.elements12911292# This computes the **negative** surface integral contribution,1293# i.e., M^{-1} * boundary_interpolation^T (which is for Gauss-Legendre DGSEM M^{-1} * L)1294# and the missing "-" is taken care of by `apply_jacobian!`.1295#1296# We also use explicit assignments instead of `+=` to let `@muladd` turn these1297# into FMAs (see comment at the top of the file).1298@threaded for element in eachelement(dg, cache)1299for l in eachnode(dg)1300for v in eachvariable(equations)1301# Aliases for repeatedly accessed variables1302surface_flux_minus = surface_flux_values[v, l, 1, element]1303surface_flux_plus = surface_flux_values[v, l, 2, element]1304for ii in eachnode(dg)1305# surface at -x1306du[v, ii, l, element] = (du[v, ii, l, element] -1307surface_flux_minus *1308boundary_interpolation_inverse_weights[ii,13091])13101311# surface at +x1312du[v, ii, l, element] = (du[v, ii, l, element] +1313surface_flux_plus *1314boundary_interpolation_inverse_weights[ii,13152])1316end13171318surface_flux_minus = surface_flux_values[v, l, 3, element]1319surface_flux_plus = surface_flux_values[v, l, 4, element]1320for jj in eachnode(dg)1321# surface at -y1322du[v, l, jj, element] = (du[v, l, jj, element] -1323surface_flux_minus *1324boundary_interpolation_inverse_weights[jj,13251])13261327# surface at +y1328du[v, l, jj, element] = (du[v, l, jj, element] +1329surface_flux_plus *1330boundary_interpolation_inverse_weights[jj,13312])1332end1333end1334end1335end13361337return nothing1338end13391340function apply_jacobian!(backend::Nothing, du, mesh::TreeMesh{2},1341equations, dg::DG, cache)1342@unpack inverse_jacobian = cache.elements13431344@threaded for element in eachelement(dg, cache)1345# Negative sign included to account for the negated surface and volume terms,1346# see e.g. the computation of `derivative_hat` in the basis setup and1347# the comment in `calc_surface_integral!`.1348factor = -inverse_jacobian[element]13491350for j in eachnode(dg), i in eachnode(dg)1351for v in eachvariable(equations)1352du[v, i, j, element] *= factor1353end1354end1355end13561357return nothing1358end13591360# Need dimension specific version to avoid error at dispatching1361function calc_sources!(du, u, t, source_terms::Nothing,1362equations::AbstractEquations{2}, dg::DG, cache)1363return nothing1364end13651366function calc_sources!(du, u, t, source_terms,1367equations::AbstractEquations{2}, dg::DG, cache)1368@unpack node_coordinates = cache.elements13691370@threaded for element in eachelement(dg, cache)1371for j in eachnode(dg), i in eachnode(dg)1372u_local = get_node_vars(u, equations, dg, i, j, element)1373x_local = get_node_coords(node_coordinates, equations, dg,1374i, j, element)1375du_local = source_terms(u_local, x_local, t, equations)1376add_to_node_vars!(du, du_local, equations, dg, i, j, element)1377end1378end13791380return nothing1381end1382end # @muladd138313841385