Path: blob/main/src/solvers/dgsem_tree/dg_1d.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 1D,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::TreeMesh{1}, 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)2324# Container cache25cache = (; elements, interfaces, boundaries)2627# Add Volume-Integral cache28cache = (; cache...,29create_cache(mesh, equations, dg.volume_integral, dg, cache, uEltype)...)3031return cache32end3334# The methods below are specialized on the volume integral type35# and called from the basic `create_cache` method at the top.3637function create_cache(mesh::Union{TreeMesh{1}, StructuredMesh{1}}, equations,38volume_integral::AbstractVolumeIntegralSubcell,39dg::DG, cache_containers, uEltype)40MA2d = MArray{Tuple{nvariables(equations), nnodes(dg) + 1},41uEltype, 2, nvariables(equations) * (nnodes(dg) + 1)}42fstar1_L_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]43fstar1_R_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]4445@threaded for t in eachindex(fstar1_L_threaded)46fstar1_L_threaded[t][:, 1] .= zero(uEltype)47fstar1_R_threaded[t][:, 1] .= zero(uEltype)48fstar1_L_threaded[t][:, nnodes(dg) + 1] .= zero(uEltype)49fstar1_R_threaded[t][:, nnodes(dg) + 1] .= zero(uEltype)50end5152return (; fstar1_L_threaded, fstar1_R_threaded)53end5455# TODO: Taal discuss/refactor timer, allowing users to pass a custom timer?5657# This function is valid for all conforming mesh types (except for `StructuredMesh`), i.e.,58# all meshes that do not involve mortar operations.59# Thus, we can use it for 1D `TreeMesh` and `UnstructuredMesh2D`.60function rhs!(du, u, t,61mesh::Union{TreeMesh{1},62UnstructuredMesh2D},63equations,64boundary_conditions, source_terms::Source,65dg::DG, cache) where {Source}66backend = trixi_backend(u)6768# Reset du69@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)7071# Calculate volume integral72@trixi_timeit timer() "volume integral" begin73calc_volume_integral!(backend, du, u, mesh,74have_nonconservative_terms(equations), equations,75dg.volume_integral, dg, cache)76end7778# Prolong solution to interfaces79@trixi_timeit timer() "prolong2interfaces" begin80prolong2interfaces!(cache, u, mesh, equations, dg)81end8283# Calculate interface fluxes84@trixi_timeit timer() "interface flux" begin85calc_interface_flux!(cache.elements.surface_flux_values, mesh,86have_nonconservative_terms(equations), equations,87dg.surface_integral, dg, cache)88end8990# Prolong solution to boundaries91@trixi_timeit timer() "prolong2boundaries" begin92prolong2boundaries!(cache, u, mesh, equations, dg)93end9495# Calculate boundary fluxes96@trixi_timeit timer() "boundary flux" begin97calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations,98dg.surface_integral, dg)99end100101# Calculate surface integrals102@trixi_timeit timer() "surface integral" begin103calc_surface_integral!(backend, du, u, mesh, equations,104dg.surface_integral, dg, cache)105end106107# Apply Jacobian from mapping to reference element108@trixi_timeit timer() "Jacobian" apply_jacobian!(backend, du, mesh, equations, dg,109cache)110111# Calculate source terms112@trixi_timeit timer() "source terms" begin113calc_sources!(du, u, t, source_terms, equations, dg, cache)114end115116return nothing117end118119#=120`weak_form_kernel!` is only implemented for conserved terms as121non-conservative terms should always be discretized in conjunction with a flux-splitting scheme,122see `flux_differencing_kernel!`.123This treatment is required to achieve, e.g., entropy-stability or well-balancedness.124See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064125=#126@inline function weak_form_kernel!(du, u,127element,128::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},129have_nonconservative_terms::False, equations,130dg::DGSEM, cache, alpha = true)131# true * [some floating point value] == [exactly the same floating point value]132# This can (hopefully) be optimized away due to constant propagation.133@unpack derivative_hat = dg.basis134135for i in eachnode(dg)136u_node = get_node_vars(u, equations, dg, i, element)137138flux1 = flux(u_node, 1, equations)139for ii in eachnode(dg)140multiply_add_to_node_vars!(du, alpha * derivative_hat[ii, i], flux1,141equations, dg, ii, element)142end143end144145return nothing146end147148@inline function flux_differencing_kernel!(du, u, element,149::Type{<:Union{TreeMesh{1},150StructuredMesh{1}}},151have_nonconservative_terms::False, equations,152volume_flux, dg::DGSEM, cache, alpha = true)153# true * [some floating point value] == [exactly the same floating point value]154# This can (hopefully) be optimized away due to constant propagation.155@unpack derivative_split = dg.basis156157# Calculate volume integral in one element158for i in eachnode(dg)159u_node = get_node_vars(u, equations, dg, i, element)160161# All diagonal entries of `derivative_split` are zero. Thus, we can skip162# the computation of the diagonal terms. In addition, we use the symmetry163# of the `volume_flux` to save half of the possible two-point flux164# computations.165166# x direction167for ii in (i + 1):nnodes(dg)168u_node_ii = get_node_vars(u, equations, dg, ii, element)169flux1 = volume_flux(u_node, u_node_ii, 1, equations)170multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], flux1,171equations, dg, i, element)172multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], flux1,173equations, dg, ii, element)174end175end176end177178@inline function flux_differencing_kernel!(du, u, element,179MeshT::Type{<:Union{TreeMesh{1},180StructuredMesh{1}}},181have_nonconservative_terms::True, equations,182volume_flux, dg::DGSEM, cache, alpha = true)183# true * [some floating point value] == [exactly the same floating point value]184# This can (hopefully) be optimized away due to constant propagation.185@unpack derivative_split = dg.basis186symmetric_flux, nonconservative_flux = volume_flux187188# Apply the symmetric flux as usual189flux_differencing_kernel!(du, u, element, MeshT, False(), equations, symmetric_flux,190dg, cache, alpha)191192# Calculate the remaining volume terms using the nonsymmetric generalized flux193for i in eachnode(dg)194u_node = get_node_vars(u, equations, dg, i, element)195196# The diagonal terms are zero since the diagonal of `derivative_split`197# is zero. We ignore this for now.198199# x direction200integral_contribution = zero(u_node)201for ii in eachnode(dg)202u_node_ii = get_node_vars(u, equations, dg, ii, element)203noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations)204integral_contribution = integral_contribution +205derivative_split[i, ii] * noncons_flux1206end207208# The factor 0.5 cancels the factor 2 in the flux differencing form209multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations,210dg, i, element)211end212end213214@inline function fv_kernel!(du, u,215MeshT::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},216have_nonconservative_terms, equations,217volume_flux_fv, dg::DGSEM, cache, element, alpha = true)218@unpack fstar1_L_threaded, fstar1_R_threaded = cache219@unpack inverse_weights = dg.basis # Plays role of inverse DG-subcell sizes220221# Calculate FV two-point fluxes222fstar1_L = fstar1_L_threaded[Threads.threadid()]223fstar1_R = fstar1_R_threaded[Threads.threadid()]224calcflux_fv!(fstar1_L, fstar1_R, u, MeshT,225have_nonconservative_terms, equations,226volume_flux_fv, dg, element, cache)227228# Calculate FV volume integral contribution229for i in eachnode(dg)230for v in eachvariable(equations)231du[v, i, element] += (alpha *232(inverse_weights[i] *233(fstar1_L[v, i + 1] - fstar1_R[v, i])))234end235end236237return nothing238end239240@inline function fvO2_kernel!(du, u,241MeshT::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},242nonconservative_terms, equations,243volume_flux_fv, dg::DGSEM, cache, element,244sc_interface_coords, reconstruction_mode, slope_limiter,245cons2recon, recon2cons,246alpha = true)247@unpack fstar1_L_threaded, fstar1_R_threaded = cache248@unpack inverse_weights = dg.basis # Plays role of inverse DG-subcell sizes249250# Calculate FV two-point fluxes251fstar1_L = fstar1_L_threaded[Threads.threadid()]252fstar1_R = fstar1_R_threaded[Threads.threadid()]253calcflux_fvO2!(fstar1_L, fstar1_R, u, MeshT, nonconservative_terms, equations,254volume_flux_fv, dg, element, cache,255sc_interface_coords, reconstruction_mode, slope_limiter,256cons2recon, recon2cons)257258# Calculate FV volume integral contribution259for i in eachnode(dg)260for v in eachvariable(equations)261du[v, i, element] += (alpha *262(inverse_weights[i] *263(fstar1_L[v, i + 1] - fstar1_R[v, i])))264end265end266267return nothing268end269270# Compute the normal flux for the FV method on subcells of the LGL subgrid, see271# Hennemann, Rueda-Ramírez, Hindenlang, Gassner (2020)272# "A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations"273# [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044)274@inline function calcflux_fv!(fstar1_L, fstar1_R, u,275::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},276have_nonconservative_terms::False,277equations, volume_flux_fv, dg::DGSEM, element, cache)278for i in 2:nnodes(dg)279u_ll = get_node_vars(u, equations, dg, i - 1, element)280u_rr = get_node_vars(u, equations, dg, i, element)281flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction282set_node_vars!(fstar1_L, flux, equations, dg, i)283set_node_vars!(fstar1_R, flux, equations, dg, i)284end285286return nothing287end288289@inline function calcflux_fv!(fstar1_L, fstar1_R, u,290::Type{<:TreeMesh{1}},291have_nonconservative_terms::True,292equations, volume_flux_fv, dg::DGSEM, element, cache)293volume_flux, nonconservative_flux = volume_flux_fv294for i in 2:nnodes(dg)295u_ll = get_node_vars(u, equations, dg, i - 1, element)296u_rr = get_node_vars(u, equations, dg, i, element)297298# Compute conservative part299f1 = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction300301# Compute nonconservative part302# Note the factor 0.5 necessary for the nonconservative fluxes based on303# the interpretation of global SBP operators coupled discontinuously via304# central fluxes/SATs305f1_L = f1 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations)306f1_R = f1 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations)307308# Copy to temporary storage309set_node_vars!(fstar1_L, f1_L, equations, dg, i)310set_node_vars!(fstar1_R, f1_R, equations, dg, i)311end312313return nothing314end315316# Compute the normal flux for the second-order FV method on subcells of the LGL subgrid, see317# Rueda-Ramírez, Hennemann, Hindenlang, Winters, & Gassner (2021)318# "An entropy stable nodal discontinuous Galerkin method for the resistive MHD equations. Part II: Subcell finite volume shock capturing"319# [JCP: 2021.110580](https://doi.org/10.1016/j.jcp.2021.110580)320@inline function calcflux_fvO2!(fstar1_L, fstar1_R, u,321::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},322nonconservative_terms::False,323equations, volume_flux_fv, dg::DGSEM, element, cache,324sc_interface_coords, reconstruction_mode, slope_limiter,325cons2recon, recon2cons)326for i in 2:nnodes(dg) # We compute FV02 fluxes at the (nnodes(dg) - 1) subcell boundaries327# Reference element:328# -1 ------------------0------------------ 1 -> x329# Gauss-Lobatto-Legendre nodes (schematic for k = 3):330# . . . .331# ^ ^ ^ ^332# Node indices:333# 1 2 3 4334# The inner subcell boundaries are governed by the335# cumulative sum of the quadrature weights - 1 .336# -1 ------------------0------------------ 1 -> x337# w1-1 (w1+w2)-1 (w1+w2+w3)-1338# | | | | |339# Note that only the inner boundaries are stored.340# Subcell interface indices, loop only over 2 -> nnodes(dg) = 4341# 1 2 3 4 5342#343# In general a four-point stencil is required, since we reconstruct the344# piecewise linear solution in both subcells next to the subcell interface.345# Since these subcell boundaries are not aligned with the DG nodes,346# on each neighboring subcell two linear solutions are reconstructed => 4 point stencil.347# For the outer interfaces the stencil shrinks since we do not consider values348# outside the element (this is a volume integral).349#350# The left subcell node values are labelled `_ll` (left-left) and `_lr` (left-right), while351# the right subcell node values are labelled `_rl` (right-left) and `_rr` (right-right).352353## Obtain unlimited values in reconstruction variables ##354355# Note: If i - 2 = 0 we do not go to neighbor element, as one would do in a finite volume scheme.356# Here, we keep it purely cell-local, thus overshoots between elements are not strictly ruled out,357# **unless** `reconstruction_mode` is set to `reconstruction_O2_inner`358u_ll = cons2recon(get_node_vars(u, equations, dg, max(1, i - 2), element),359equations)360u_lr = cons2recon(get_node_vars(u, equations, dg, i - 1, element),361equations)362u_rl = cons2recon(get_node_vars(u, equations, dg, i, element),363equations)364# Note: If i + 1 > nnodes(dg) we do not go to neighbor element, as one would do in a finite volume scheme.365# Here, we keep it purely cell-local, thus overshoots between elements are not strictly ruled out,366# **unless** `reconstruction_mode` is set to `reconstruction_O2_inner`367u_rr = cons2recon(get_node_vars(u, equations, dg, min(nnodes(dg), i + 1),368element), equations)369370## Reconstruct values at interfaces with limiting ##371u_l, u_r = reconstruction_mode(u_ll, u_lr, u_rl, u_rr,372sc_interface_coords, i,373slope_limiter, dg)374375## Convert reconstruction variables back to conservative variables ##376flux = volume_flux_fv(recon2cons(u_l, equations), recon2cons(u_r, equations),3771, equations) # orientation 1: x direction378379set_node_vars!(fstar1_L, flux, equations, dg, i)380set_node_vars!(fstar1_R, flux, equations, dg, i)381end382383return nothing384end385386# Used for both the purely hyperbolic conserved variables `u`387# and the parabolic flux in x-direction in the 1D parabolic case.388function prolong2interfaces!(cache, u_or_flux_parabolic,389mesh::TreeMesh{1}, equations, dg::DG)390@unpack interfaces = cache391@unpack neighbor_ids = interfaces392interfaces_u = interfaces.u393394@threaded for interface in eachinterface(dg, cache)395left_element = neighbor_ids[1, interface]396right_element = neighbor_ids[2, interface]397398# interface in x-direction399for v in eachvariable(equations)400interfaces_u[1, v, interface] = u_or_flux_parabolic[v, nnodes(dg),401left_element]402interfaces_u[2, v, interface] = u_or_flux_parabolic[v, 1, right_element]403end404end405406return nothing407end408409function prolong2interfaces!(cache, u_or_flux_parabolic,410mesh::TreeMesh{1}, equations,411dg::DGSEM{<:GaussLegendreBasis})412@unpack interfaces = cache413@unpack neighbor_ids = interfaces414@unpack boundary_interpolation = dg.basis415interfaces_u = interfaces.u416417@threaded for interface in eachinterface(dg, cache)418left_element = neighbor_ids[1, interface]419right_element = neighbor_ids[2, interface]420421# interface in x-direction422for v in eachvariable(equations)423# Interpolate to the interfaces using a local variable for424# the accumulation of values (to reduce global memory operations).425interface_u_1 = zero(eltype(interfaces_u))426interface_u_2 = zero(eltype(interfaces_u))427for ii in eachnode(dg)428# Not += to allow `@muladd` to turn these into FMAs429# (see comment at the top of the file)430# Need `boundary_interpolation` at right (+1) node for left element431interface_u_1 = (interface_u_1 +432u_or_flux_parabolic[v, ii, left_element] *433boundary_interpolation[ii, 2])434# Need `boundary_interpolation` at left (-1) node for right element435interface_u_2 = (interface_u_2 +436u_or_flux_parabolic[v, ii, right_element] *437boundary_interpolation[ii, 1])438end439interfaces_u[1, v, interface] = interface_u_1440interfaces_u[2, v, interface] = interface_u_2441end442end443444return nothing445end446447function calc_interface_flux!(surface_flux_values,448mesh::TreeMesh{1},449have_nonconservative_terms::False, equations,450surface_integral, dg::DG, cache)451@unpack surface_flux = surface_integral452@unpack u, neighbor_ids, orientations = cache.interfaces453454@threaded for interface in eachinterface(dg, cache)455# Get neighboring elements456left_id = neighbor_ids[1, interface]457right_id = neighbor_ids[2, interface]458459# Determine interface direction with respect to elements:460# orientation = 1: left -> 2, right -> 1461left_direction = 2 * orientations[interface]462right_direction = 2 * orientations[interface] - 1463464# Call pointwise Riemann solver465u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface)466flux = surface_flux(u_ll, u_rr, orientations[interface], equations)467468# Copy flux to left and right element storage469for v in eachvariable(equations)470surface_flux_values[v, left_direction, left_id] = flux[v]471surface_flux_values[v, right_direction, right_id] = flux[v]472end473end474475return nothing476end477478function calc_interface_flux!(surface_flux_values,479mesh::TreeMesh{1},480have_nonconservative_terms::True, equations,481surface_integral, dg::DG, cache)482surface_flux, nonconservative_flux = surface_integral.surface_flux483@unpack u, neighbor_ids, orientations = cache.interfaces484485@threaded for interface in eachinterface(dg, cache)486# Get neighboring elements487left_id = neighbor_ids[1, interface]488right_id = neighbor_ids[2, interface]489490# Determine interface direction with respect to elements:491# orientation = 1: left -> 2, right -> 1492# orientation = 2: left -> 4, right -> 3493left_direction = 2 * orientations[interface]494right_direction = 2 * orientations[interface] - 1495496# Call pointwise Riemann solver497orientation = orientations[interface]498u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface)499flux = surface_flux(u_ll, u_rr, orientation, equations)500501# Compute both nonconservative fluxes502noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations)503noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations)504505# Copy flux to left and right element storage506for v in eachvariable(equations)507# Note the factor 0.5 necessary for the nonconservative fluxes based on508# the interpretation of global SBP operators coupled discontinuously via509# central fluxes/SATs510surface_flux_values[v, left_direction, left_id] = flux[v] +5110.5f0 * noncons_left[v]512surface_flux_values[v, right_direction, right_id] = flux[v] +5130.5f0 * noncons_right[v]514end515end516517return nothing518end519520# Used for both the purely hyperbolic conserved variables `u`521# and the parabolic flux in x-direction in the 1D parabolic case.522function prolong2boundaries!(cache, u_or_flux_parabolic,523mesh::TreeMesh{1}, equations, dg::DG)524@unpack boundaries = cache525@unpack neighbor_sides = boundaries526527@threaded for boundary in eachboundary(dg, cache)528element = boundaries.neighbor_ids[boundary]529530# boundary in x-direction531if neighbor_sides[boundary] == 1532# element in -x direction of boundary533for v in eachvariable(equations)534boundaries.u[1, v, boundary] = u_or_flux_parabolic[v, nnodes(dg),535element]536end537else # Element in +x direction of boundary538for v in eachvariable(equations)539boundaries.u[2, v, boundary] = u_or_flux_parabolic[v, 1, element]540end541end542end543544return nothing545end546547function prolong2boundaries!(cache, u_or_flux_parabolic,548mesh::TreeMesh{1}, equations,549dg::DGSEM{<:GaussLegendreBasis})550@unpack boundaries = cache551@unpack neighbor_sides = boundaries552@unpack boundary_interpolation = dg.basis553554@threaded for boundary in eachboundary(dg, cache)555element = boundaries.neighbor_ids[boundary]556557# boundary in x-direction558if neighbor_sides[boundary] == 1559# element in -x direction of boundary => need to evaluate at right boundary node (+1)560for v in eachvariable(equations)561# Interpolate to the boundaries using a local variable for562# the accumulation of values (to reduce global memory operations).563boundary_u_1 = zero(eltype(boundaries.u))564for ii in eachnode(dg)565# Not += to allow `@muladd` to turn these into FMAs566# (see comment at the top of the file)567boundary_u_1 = (boundary_u_1 +568u_or_flux_parabolic[v, ii, element] *569boundary_interpolation[ii, 2])570end571boundaries.u[1, v, boundary] = boundary_u_1572end573else # Element in +x direction of boundary => need to evaluate at left boundary node (-1)574for v in eachvariable(equations)575boundary_u_2 = zero(eltype(boundaries.u))576for ii in eachnode(dg)577boundary_u_2 = (boundary_u_2 +578u_or_flux_parabolic[v, ii, element] *579boundary_interpolation[ii, 1])580end581boundaries.u[2, v, boundary] = boundary_u_2582end583end584end585586return nothing587end588589function calc_boundary_flux!(cache, t, boundary_conditions::NamedTuple,590mesh::TreeMesh{1}, equations, surface_integral, dg::DG)591@unpack surface_flux_values = cache.elements592@unpack n_boundaries_per_direction = cache.boundaries593594# Calculate indices595lasts = accumulate(+, n_boundaries_per_direction)596firsts = lasts - n_boundaries_per_direction .+ 1597598# Calc boundary fluxes in each direction599calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[1],600have_nonconservative_terms(equations), equations,601surface_integral, dg, cache,6021, firsts[1], lasts[1])603calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[2],604have_nonconservative_terms(equations), equations,605surface_integral, dg, cache,6062, firsts[2], lasts[2])607608return nothing609end610611function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 3},612t,613boundary_condition,614have_nonconservative_terms::False, equations,615surface_integral, dg::DG, cache,616direction, first_boundary, last_boundary)617@unpack surface_flux = surface_integral618@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries619620@threaded for boundary in first_boundary:last_boundary621# Get neighboring element622neighbor = neighbor_ids[boundary]623624# Get boundary flux625u_ll, u_rr = get_surface_node_vars(u, equations, dg, boundary)626if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right627u_inner = u_ll628else # Element is on the right, boundary on the left629u_inner = u_rr630end631x = get_node_coords(node_coordinates, equations, dg, boundary)632flux = boundary_condition(u_inner, orientations[boundary], direction, x, t,633surface_flux, equations)634635# Copy flux to left and right element storage636for v in eachvariable(equations)637surface_flux_values[v, direction, neighbor] = flux[v]638end639end640641return nothing642end643644function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 3},645t,646boundary_condition,647have_nonconservative_terms::True, equations,648surface_integral, dg::DG, cache,649direction, first_boundary, last_boundary)650@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries651652@threaded for boundary in first_boundary:last_boundary653# Get neighboring element654neighbor = neighbor_ids[boundary]655656# Get boundary flux657u_ll, u_rr = get_surface_node_vars(u, equations, dg, boundary)658if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right659u_inner = u_ll660else # Element is on the right, boundary on the left661u_inner = u_rr662end663x = get_node_coords(node_coordinates, equations, dg, boundary)664665flux, noncons_flux = boundary_condition(u_inner, orientations[boundary],666direction, x, t,667surface_integral.surface_flux,668equations)669670# Copy flux to left and right element storage671for v in eachvariable(equations)672surface_flux_values[v, direction, neighbor] = flux[v] +6730.5f0 * noncons_flux[v]674end675end676677return nothing678end679680function calc_surface_integral!(backend::Nothing, du, u,681mesh::Union{TreeMesh{1}, StructuredMesh{1}},682equations, surface_integral::SurfaceIntegralWeakForm,683dg::DGSEM, cache)684@unpack inverse_weights = dg.basis685@unpack surface_flux_values = cache.elements686687# This computes the **negative** surface integral contribution,688# i.e., M^{-1} * boundary_interpolation^T (which is for Gauss-Lobatto DGSEM just M^{-1} * B)689# and the missing "-" is taken care of by `apply_jacobian!`.690#691# We also use explicit assignments instead of `+=` to let `@muladd` turn these692# into FMAs (see comment at the top of the file).693factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±1694@threaded for element in eachelement(dg, cache)695for v in eachvariable(equations)696# surface at -x697du[v, 1, element] = (du[v, 1, element] -698surface_flux_values[v, 1, element] *699factor)700701# surface at +x702du[v, nnodes(dg), element] = (du[v, nnodes(dg), element] +703surface_flux_values[v, 2, element] *704factor)705end706end707708return nothing709end710711function calc_surface_integral!(backend::Nothing, du, u,712mesh::Union{TreeMesh{1}, StructuredMesh{1}},713equations, surface_integral::SurfaceIntegralWeakForm,714dg::DGSEM{<:GaussLegendreBasis}, cache)715@unpack boundary_interpolation_inverse_weights = dg.basis716@unpack surface_flux_values = cache.elements717718# This computes the **negative** surface integral contribution,719# i.e., M^{-1} * boundary_interpolation^T (which is for Gauss-Legendre DGSEM M^{-1} * L)720# and the missing "-" is taken care of by `apply_jacobian!`.721#722# We also use explicit assignments instead of `+=` to let `@muladd` turn these723# into FMAs (see comment at the top of the file).724@threaded for element in eachelement(dg, cache)725for v in eachvariable(equations)726# Aliases for repeatedly accessed variables727surface_flux_minus = surface_flux_values[v, 1, element]728surface_flux_plus = surface_flux_values[v, 2, element]729for ii in eachnode(dg)730# surface at -x731du[v, ii, element] = (du[v, ii, element] -732surface_flux_minus *733boundary_interpolation_inverse_weights[ii, 1])734735# surface at +x736du[v, ii, element] = (du[v, ii, element] +737surface_flux_plus *738boundary_interpolation_inverse_weights[ii, 2])739end740end741end742743return nothing744end745746function apply_jacobian!(backend::Nothing, du, mesh::TreeMesh{1},747equations, dg::DG, cache)748@unpack inverse_jacobian = cache.elements749750@threaded for element in eachelement(dg, cache)751# Negative sign included to account for the negated surface and volume terms,752# see e.g. the computation of `derivative_hat` in the basis setup and753# the comment in `calc_surface_integral!`.754factor = -inverse_jacobian[element]755756for i in eachnode(dg)757for v in eachvariable(equations)758du[v, i, element] *= factor759end760end761end762763return nothing764end765766# Need dimension specific version to avoid error at dispatching767function calc_sources!(du, u, t, source_terms::Nothing,768equations::AbstractEquations{1}, dg::DG, cache)769return nothing770end771772function calc_sources!(du, u, t, source_terms,773equations::AbstractEquations{1}, dg::DG, cache)774@unpack node_coordinates = cache.elements775776@threaded for element in eachelement(dg, cache)777for i in eachnode(dg)778u_local = get_node_vars(u, equations, dg, i, element)779x_local = get_node_coords(node_coordinates, equations, dg,780i, element)781du_local = source_terms(u_local, x_local, t, equations)782add_to_node_vars!(du, du_local, equations, dg, i, element)783end784end785786return nothing787end788end # @muladd789790791