Path: blob/main/src/solvers/dgsem_tree/dg_2d_parallel.jl
5590 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 using MPI,8# currently limited to Lobatto-Legendre nodes910# TODO: MPI dimension agnostic11mutable struct MPICache{uEltype <: Real}12mpi_neighbor_ranks::Vector{Int}13mpi_neighbor_interfaces::Vector{Vector{Int}}14mpi_neighbor_mortars::Vector{Vector{Int}}15mpi_send_buffers::Vector{Vector{uEltype}}16mpi_recv_buffers::Vector{Vector{uEltype}}17mpi_send_requests::Vector{MPI.Request}18mpi_recv_requests::Vector{MPI.Request}19n_elements_by_rank::OffsetArray{Int, 1, Array{Int, 1}}20n_elements_global::Int21first_element_global_id::Int22end2324function MPICache(uEltype)25# MPI communication "just works" for bitstypes only26if !isbitstype(uEltype)27throw(ArgumentError("MPICache only supports bitstypes, $uEltype is not a bitstype."))28end29mpi_neighbor_ranks = Vector{Int}(undef, 0)30mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, 0)31mpi_neighbor_mortars = Vector{Vector{Int}}(undef, 0)32mpi_send_buffers = Vector{Vector{uEltype}}(undef, 0)33mpi_recv_buffers = Vector{Vector{uEltype}}(undef, 0)34mpi_send_requests = Vector{MPI.Request}(undef, 0)35mpi_recv_requests = Vector{MPI.Request}(undef, 0)36n_elements_by_rank = OffsetArray(Vector{Int}(undef, 0), 0:-1)37n_elements_global = 038first_element_global_id = 03940return MPICache{uEltype}(mpi_neighbor_ranks, mpi_neighbor_interfaces,41mpi_neighbor_mortars,42mpi_send_buffers, mpi_recv_buffers,43mpi_send_requests, mpi_recv_requests,44n_elements_by_rank, n_elements_global,45first_element_global_id)46end47@inline Base.eltype(::MPICache{uEltype}) where {uEltype} = uEltype4849# TODO: MPI dimension agnostic50function start_mpi_receive!(mpi_cache::MPICache)51for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks)52mpi_cache.mpi_recv_requests[index] = MPI.Irecv!(mpi_cache.mpi_recv_buffers[index],53rank, rank, mpi_comm())54end5556return nothing57end5859# TODO: MPI dimension agnostic60function start_mpi_send!(mpi_cache::MPICache, mesh, equations, dg, cache)61data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1)6263for rank in 1:length(mpi_cache.mpi_neighbor_ranks)64send_buffer = mpi_cache.mpi_send_buffers[rank]6566for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[rank])67first = (index - 1) * data_size + 168last = (index - 1) * data_size + data_size6970if cache.mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction71@views send_buffer[first:last] .= vec(cache.mpi_interfaces.u[2, :, :,72interface])73else # local element in negative direction74@views send_buffer[first:last] .= vec(cache.mpi_interfaces.u[1, :, :,75interface])76end77end7879# Each mortar has a total size of 4 * data_size, set everything to NaN first and overwrite the80# parts where local data exists81interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[rank]) *82data_size83mortars_data_size = length(mpi_cache.mpi_neighbor_mortars[rank]) * 4 * data_size84send_buffer[(interfaces_data_size + 1):(interfaces_data_size + mortars_data_size)] .= NaN8586for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[rank])87# First and last indices in the send buffer for mortar data obtained from local element88# in a given position89index_base = interfaces_data_size + (index - 1) * 4 * data_size90indices = (91# first, last for local element in position 1 (lower element)92(index_base + 1,93index_base + 1 * data_size),94# first, last for local element in position 2 (upper element)95(index_base + 1 * data_size + 1,96index_base + 2 * data_size),97# firsts, lasts for local element in position 3 (large element)98(index_base + 2 * data_size + 1,99index_base + 3 * data_size,100index_base + 3 * data_size + 1,101index_base + 4 * data_size))102103for position in cache.mpi_mortars.local_neighbor_positions[mortar]104# Determine whether the data belongs to the left or right side105if cache.mpi_mortars.large_sides[mortar] == 1 # large element on left side106if position in (1, 2) # small element107leftright = 2108else # large element109leftright = 1110end111else # large element on right side112if position in (1, 2) # small element113leftright = 1114else # large element115leftright = 2116end117end118# copy data to buffer119if position == 1 # lower element120first, last = indices[position]121@views send_buffer[first:last] .= vec(cache.mpi_mortars.u_lower[leftright,122:,123:,124mortar])125elseif position == 2 # upper element126first, last = indices[position]127@views send_buffer[first:last] .= vec(cache.mpi_mortars.u_upper[leftright,128:,129:,130mortar])131else # large element132first_lower, last_lower, first_upper, last_upper = indices[position]133@views send_buffer[first_lower:last_lower] .= vec(cache.mpi_mortars.u_lower[leftright,134:,135:,136mortar])137@views send_buffer[first_upper:last_upper] .= vec(cache.mpi_mortars.u_upper[leftright,138:,139:,140mortar])141end142end143end144end145146# Start sending147for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks)148mpi_cache.mpi_send_requests[index] = MPI.Isend(mpi_cache.mpi_send_buffers[index],149rank, mpi_rank(), mpi_comm())150end151152return nothing153end154155# TODO: MPI dimension agnostic156function finish_mpi_send!(mpi_cache::MPICache)157return MPI.Waitall(mpi_cache.mpi_send_requests, MPI.Status)158end159160# TODO: MPI dimension agnostic161function finish_mpi_receive!(mpi_cache::MPICache, mesh, equations, dg, cache)162data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1)163164# Start receiving and unpack received data until all communication is finished165data = MPI.Waitany(mpi_cache.mpi_recv_requests)166while data !== nothing167recv_buffer = mpi_cache.mpi_recv_buffers[data]168169for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[data])170first = (index - 1) * data_size + 1171last = (index - 1) * data_size + data_size172173if cache.mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction174@views vec(cache.mpi_interfaces.u[1, :, :, interface]) .= recv_buffer[first:last]175else # local element in negative direction176@views vec(cache.mpi_interfaces.u[2, :, :, interface]) .= recv_buffer[first:last]177end178end179180interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[data]) *181data_size182for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[data])183# First and last indices in the receive buffer for mortar data obtained from remote element184# in a given position185index_base = interfaces_data_size + (index - 1) * 4 * data_size186indices = (187# first, last for local element in position 1 (lower element)188(index_base + 1,189index_base + 1 * data_size),190# first, last for local element in position 2 (upper element)191(index_base + 1 * data_size + 1,192index_base + 2 * data_size),193# firsts, lasts for local element in position 3 (large element)194(index_base + 2 * data_size + 1,195index_base + 3 * data_size,196index_base + 3 * data_size + 1,197index_base + 4 * data_size))198199for position in 1:3200# Skip if received data for `pos` is NaN as no real data has been sent for the201# corresponding element202if isnan(recv_buffer[Base.first(indices[position])])203continue204end205206# Determine whether the received data belongs to the left or right side207if cache.mpi_mortars.large_sides[mortar] == 1 # large element on left side208if position in (1, 2) # small element209leftright = 2210else # large element211leftright = 1212end213else # large element on right side214if position in (1, 2) # small element215leftright = 1216else # large element217leftright = 2218end219end220221if position == 1 # lower element data has been received222first, last = indices[position]223@views vec(cache.mpi_mortars.u_lower[leftright, :, :, mortar]) .= recv_buffer[first:last]224elseif position == 2 # upper element data has been received225first, last = indices[position]226@views vec(cache.mpi_mortars.u_upper[leftright, :, :, mortar]) .= recv_buffer[first:last]227else # large element data has been received228first_lower, last_lower, first_upper, last_upper = indices[position]229@views vec(cache.mpi_mortars.u_lower[leftright, :, :, mortar]) .= recv_buffer[first_lower:last_lower]230@views vec(cache.mpi_mortars.u_upper[leftright, :, :, mortar]) .= recv_buffer[first_upper:last_upper]231end232end233end234235data = MPI.Waitany(mpi_cache.mpi_recv_requests)236end237238return nothing239end240241# This method is called when a SemidiscretizationHyperbolic is constructed.242# It constructs the basic `cache` used throughout the simulation to compute243# the RHS etc.244function create_cache(mesh::TreeMeshParallel{2}, equations,245dg::DG, RealT, ::Type{uEltype}) where {uEltype <: Real}246# Get cells for which an element needs to be created (i.e. all leaf cells)247leaf_cell_ids = local_leaf_cells(mesh.tree)248249elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype)250251interfaces = init_interfaces(leaf_cell_ids, mesh, elements)252253mpi_interfaces = init_mpi_interfaces(leaf_cell_ids, mesh, elements)254255boundaries = init_boundaries(leaf_cell_ids, mesh, elements, dg.basis)256257mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar)258259mpi_mortars = init_mpi_mortars(leaf_cell_ids, mesh, elements, dg.mortar)260261mpi_cache = init_mpi_cache(mesh, elements, mpi_interfaces, mpi_mortars,262nvariables(equations), nnodes(dg), uEltype)263264# Container cache265cache = (; elements, interfaces, mpi_interfaces, boundaries, mortars,266mpi_mortars, mpi_cache)267268# Add Volume-Integral cache269cache = (; cache...,270create_cache(mesh, equations, dg.volume_integral, dg, cache, uEltype)...)271# Add Mortar cache272cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...)273274return cache275end276277function init_mpi_cache(mesh, elements, mpi_interfaces, mpi_mortars, nvars, nnodes,278uEltype)279mpi_cache = MPICache(uEltype)280281init_mpi_cache!(mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, nvars,282nnodes, uEltype)283return mpi_cache284end285286function init_mpi_cache!(mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, nvars,287nnodes, uEltype)288mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars = init_mpi_neighbor_connectivity(elements,289mpi_interfaces,290mpi_mortars,291mesh)292293mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests = init_mpi_data_structures(mpi_neighbor_interfaces,294mpi_neighbor_mortars,295ndims(mesh),296nvars,297nnodes,298uEltype)299300# Determine local and total number of elements301n_elements_by_rank = Vector{Int}(undef, mpi_nranks())302n_elements_by_rank[mpi_rank() + 1] = nelements(elements)303MPI.Allgather!(MPI.UBuffer(n_elements_by_rank, 1), mpi_comm())304n_elements_by_rank = OffsetArray(n_elements_by_rank, 0:(mpi_nranks() - 1))305n_elements_global = MPI.Allreduce(nelements(elements), +, mpi_comm())306@assert n_elements_global==sum(n_elements_by_rank) "error in total number of elements"307308# Determine the global element id of the first element309first_element_global_id = MPI.Exscan(nelements(elements), +, mpi_comm())310if mpi_isroot()311# With Exscan, the result on the first rank is undefined312first_element_global_id = 1313else314# On all other ranks we need to add one, since Julia has one-based indices315first_element_global_id += 1316end317# TODO reuse existing structures318@pack! mpi_cache = mpi_neighbor_ranks, mpi_neighbor_interfaces,319mpi_neighbor_mortars,320mpi_send_buffers, mpi_recv_buffers,321mpi_send_requests, mpi_recv_requests,322n_elements_by_rank, n_elements_global,323first_element_global_id324end325326# Initialize connectivity between MPI neighbor ranks327function init_mpi_neighbor_connectivity(elements, mpi_interfaces, mpi_mortars,328mesh::TreeMesh2D)329tree = mesh.tree330331# Determine neighbor ranks and sides for MPI interfaces332neighbor_ranks_interface = fill(-1, nmpiinterfaces(mpi_interfaces))333# The global interface id is the smaller of the (globally unique) neighbor cell ids, multiplied by334# number of directions (2 * ndims) plus direction minus one335global_interface_ids = fill(-1, nmpiinterfaces(mpi_interfaces))336for interface_id in 1:nmpiinterfaces(mpi_interfaces)337orientation = mpi_interfaces.orientations[interface_id]338remote_side = mpi_interfaces.remote_sides[interface_id]339# Direction is from local cell to remote cell340if orientation == 1 # MPI interface in x-direction341if remote_side == 1 # remote cell on the "left" of MPI interface342direction = 1343else # remote cell on the "right" of MPI interface344direction = 2345end346else # MPI interface in y-direction347if remote_side == 1 # remote cell on the "left" of MPI interface348direction = 3349else # remote cell on the "right" of MPI interface350direction = 4351end352end353local_neighbor_id = mpi_interfaces.local_neighbor_ids[interface_id]354local_cell_id = elements.cell_ids[local_neighbor_id]355remote_cell_id = tree.neighbor_ids[direction, local_cell_id]356neighbor_ranks_interface[interface_id] = tree.mpi_ranks[remote_cell_id]357if local_cell_id < remote_cell_id358global_interface_ids[interface_id] = 2 * ndims(tree) * local_cell_id +359direction - 1360else361global_interface_ids[interface_id] = (2 * ndims(tree) * remote_cell_id +362opposite_direction(direction) - 1)363end364end365366# Determine neighbor ranks for MPI mortars367neighbor_ranks_mortar = Vector{Vector{Int}}(undef, nmpimortars(mpi_mortars))368# The global mortar id is the (globally unique) large cell id, multiplied by369# number of directions (2 * ndims) plus direction minus one where370# direction = 1 for mortars in x-direction where large element is left371# direction = 2 for mortars in x-direction where large element is right372# direction = 3 for mortars in y-direction where large element is left373# direction = 4 for mortars in y-direction where large element is right374global_mortar_ids = fill(-1, nmpimortars(mpi_mortars))375for mortar in 1:nmpimortars(mpi_mortars)376neighbor_ranks_mortar[mortar] = Vector{Int}()377378orientation = mpi_mortars.orientations[mortar]379large_side = mpi_mortars.large_sides[mortar]380direction = (orientation - 1) * 2 + large_side381382local_neighbor_ids = mpi_mortars.local_neighbor_ids[mortar]383local_neighbor_positions = mpi_mortars.local_neighbor_positions[mortar]384if 3 in local_neighbor_positions # large element is on this rank385large_element_id = local_neighbor_ids[findfirst(pos -> pos == 3,386local_neighbor_positions)]387large_cell_id = elements.cell_ids[large_element_id]388else # large element is remote389cell_id = elements.cell_ids[first(local_neighbor_ids)]390large_cell_id = tree.neighbor_ids[direction, tree.parent_ids[cell_id]]391end392393neighbor_cell_id = tree.neighbor_ids[opposite_direction(direction),394large_cell_id]395if direction == 1396lower_cell_id = tree.child_ids[1, neighbor_cell_id]397upper_cell_id = tree.child_ids[3, neighbor_cell_id]398elseif direction == 2399lower_cell_id = tree.child_ids[2, neighbor_cell_id]400upper_cell_id = tree.child_ids[4, neighbor_cell_id]401elseif direction == 3402lower_cell_id = tree.child_ids[1, neighbor_cell_id]403upper_cell_id = tree.child_ids[2, neighbor_cell_id]404else405lower_cell_id = tree.child_ids[3, neighbor_cell_id]406upper_cell_id = tree.child_ids[4, neighbor_cell_id]407end408409for cell_id in (lower_cell_id, upper_cell_id, large_cell_id)410if !is_own_cell(tree, cell_id)411neighbor_rank = tree.mpi_ranks[cell_id]412if !(neighbor_rank in neighbor_ranks_mortar[mortar])413push!(neighbor_ranks_mortar[mortar], neighbor_rank)414end415end416end417418global_mortar_ids[mortar] = 2 * ndims(tree) * large_cell_id + direction - 1419end420421# Get sorted, unique neighbor ranks422mpi_neighbor_ranks = vcat(neighbor_ranks_interface, neighbor_ranks_mortar...) |>423sort |> unique424425# Sort interfaces by global interface id426p = sortperm(global_interface_ids)427neighbor_ranks_interface .= neighbor_ranks_interface[p]428interface_ids = collect(1:nmpiinterfaces(mpi_interfaces))[p]429430# Sort mortars by global mortar id431p = sortperm(global_mortar_ids)432neighbor_ranks_mortar .= neighbor_ranks_mortar[p]433mortar_ids = collect(1:nmpimortars(mpi_mortars))[p]434435# For each neighbor rank, init connectivity data structures436mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks))437mpi_neighbor_mortars = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks))438for (index, rank) in enumerate(mpi_neighbor_ranks)439mpi_neighbor_interfaces[index] = interface_ids[findall(x -> (x == rank),440neighbor_ranks_interface)]441mpi_neighbor_mortars[index] = mortar_ids[findall(x -> (rank in x),442neighbor_ranks_mortar)]443end444445# Sanity checks that we counted all interfaces exactly once446@assert sum(length(v) for v in mpi_neighbor_interfaces) ==447nmpiinterfaces(mpi_interfaces)448449return mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars450end451452function rhs!(du, u, t,453mesh::Union{TreeMeshParallel{2}, P4estMeshParallel{2},454T8codeMeshParallel{2}}, equations,455boundary_conditions, source_terms::Source,456dg::DG, cache) where {Source}457backend = trixi_backend(u)458459# Start to receive MPI data460@trixi_timeit timer() "start MPI receive" start_mpi_receive!(cache.mpi_cache)461462# Prolong solution to MPI interfaces463@trixi_timeit timer() "prolong2mpiinterfaces" begin464prolong2mpiinterfaces!(cache, u, mesh, equations, dg.surface_integral, dg)465end466467# Prolong solution to MPI mortars468@trixi_timeit timer() "prolong2mpimortars" begin469prolong2mpimortars!(cache, u, mesh, equations,470dg.mortar, dg)471end472473# Start to send MPI data474@trixi_timeit timer() "start MPI send" begin475start_mpi_send!(cache.mpi_cache, mesh, equations, dg, cache)476end477478# Reset du479@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)480481# Calculate volume integral482@trixi_timeit timer() "volume integral" begin483calc_volume_integral!(backend, du, u, mesh,484have_nonconservative_terms(equations), equations,485dg.volume_integral, dg, cache)486end487488# Prolong solution to interfaces489# TODO: Taal decide order of arguments, consistent vs. modified cache first?490@trixi_timeit timer() "prolong2interfaces" begin491prolong2interfaces!(backend, cache, u, mesh, equations, dg)492end493494# Calculate interface fluxes495@trixi_timeit timer() "interface flux" begin496calc_interface_flux!(backend, cache.elements.surface_flux_values, mesh,497have_nonconservative_terms(equations), equations,498dg.surface_integral, dg, cache)499end500501# Prolong solution to boundaries502@trixi_timeit timer() "prolong2boundaries" begin503prolong2boundaries!(cache, u, mesh, equations, dg)504end505506# Calculate boundary fluxes507@trixi_timeit timer() "boundary flux" begin508calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations,509dg.surface_integral, dg)510end511512# Prolong solution to mortars513@trixi_timeit timer() "prolong2mortars" begin514prolong2mortars!(cache, u, mesh, equations,515dg.mortar, dg)516end517518# Calculate mortar fluxes519@trixi_timeit timer() "mortar flux" begin520calc_mortar_flux!(cache.elements.surface_flux_values, mesh,521have_nonconservative_terms(equations), equations,522dg.mortar, dg.surface_integral, dg, cache)523end524525# Finish to receive MPI data526@trixi_timeit timer() "finish MPI receive" begin527finish_mpi_receive!(cache.mpi_cache, mesh, equations, dg, cache)528end529530# Calculate MPI interface fluxes531@trixi_timeit timer() "MPI interface flux" begin532calc_mpi_interface_flux!(cache.elements.surface_flux_values, mesh,533have_nonconservative_terms(equations), equations,534dg.surface_integral, dg, cache)535end536537# Calculate MPI mortar fluxes538@trixi_timeit timer() "MPI mortar flux" begin539calc_mpi_mortar_flux!(cache.elements.surface_flux_values, mesh,540have_nonconservative_terms(equations), equations,541dg.mortar, dg.surface_integral, dg, cache)542end543544# Calculate surface integrals545@trixi_timeit timer() "surface integral" begin546calc_surface_integral!(backend, du, u, mesh, equations,547dg.surface_integral, dg, cache)548end549550# Apply Jacobian from mapping to reference element551@trixi_timeit timer() "Jacobian" apply_jacobian!(backend, du, mesh, equations, dg,552cache)553554# Calculate source terms555@trixi_timeit timer() "source terms" begin556calc_sources!(du, u, t, source_terms, equations, dg, cache)557end558559# Finish to send MPI data560@trixi_timeit timer() "finish MPI send" finish_mpi_send!(cache.mpi_cache)561562return nothing563end564565function prolong2mpiinterfaces!(cache, u,566mesh::TreeMeshParallel{2},567equations, surface_integral, dg::DG)568@unpack mpi_interfaces = cache569570@threaded for interface in eachmpiinterface(dg, cache)571local_element = mpi_interfaces.local_neighbor_ids[interface]572573if mpi_interfaces.orientations[interface] == 1 # interface in x-direction574if mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction575for j in eachnode(dg), v in eachvariable(equations)576mpi_interfaces.u[2, v, j, interface] = u[v, 1, j, local_element]577end578else # local element in negative direction579for j in eachnode(dg), v in eachvariable(equations)580mpi_interfaces.u[1, v, j, interface] = u[v, nnodes(dg), j,581local_element]582end583end584else # interface in y-direction585if mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction586for i in eachnode(dg), v in eachvariable(equations)587mpi_interfaces.u[2, v, i, interface] = u[v, i, 1, local_element]588end589else # local element in negative direction590for i in eachnode(dg), v in eachvariable(equations)591mpi_interfaces.u[1, v, i, interface] = u[v, i, nnodes(dg),592local_element]593end594end595end596end597598return nothing599end600601function prolong2mpimortars!(cache, u,602mesh::TreeMeshParallel{2}, equations,603mortar_l2::LobattoLegendreMortarL2,604dg::DGSEM)605@unpack mpi_mortars = cache606607@threaded for mortar in eachmpimortar(dg, cache)608local_neighbor_ids = mpi_mortars.local_neighbor_ids[mortar]609local_neighbor_positions = mpi_mortars.local_neighbor_positions[mortar]610611for (element, position) in zip(local_neighbor_ids, local_neighbor_positions)612if position in (1, 2) # Current element is small613# Copy solution small to small614if mpi_mortars.large_sides[mortar] == 1 # -> small elements on right side615if mpi_mortars.orientations[mortar] == 1616# L2 mortars in x-direction617if position == 1618for l in eachnode(dg)619for v in eachvariable(equations)620mpi_mortars.u_lower[2, v, l, mortar] = u[v, 1, l,621element]622end623end624else # position == 2625for l in eachnode(dg)626for v in eachvariable(equations)627mpi_mortars.u_upper[2, v, l, mortar] = u[v, 1, l,628element]629end630end631end632else633# L2 mortars in y-direction634if position == 1635for l in eachnode(dg)636for v in eachvariable(equations)637mpi_mortars.u_lower[2, v, l, mortar] = u[v, l, 1,638element]639end640end641else # position == 2642for l in eachnode(dg)643for v in eachvariable(equations)644mpi_mortars.u_upper[2, v, l, mortar] = u[v, l, 1,645element]646end647end648end649end650else # large_sides[mortar] == 2 -> small elements on left side651if mpi_mortars.orientations[mortar] == 1652# L2 mortars in x-direction653if position == 1654for l in eachnode(dg)655for v in eachvariable(equations)656mpi_mortars.u_lower[1, v, l, mortar] = u[v,657nnodes(dg),658l, element]659end660end661else # position == 2662for l in eachnode(dg)663for v in eachvariable(equations)664mpi_mortars.u_upper[1, v, l, mortar] = u[v,665nnodes(dg),666l, element]667end668end669end670else671# L2 mortars in y-direction672if position == 1673for l in eachnode(dg)674for v in eachvariable(equations)675mpi_mortars.u_lower[1, v, l, mortar] = u[v, l,676nnodes(dg),677element]678end679end680else # position == 2681for l in eachnode(dg)682for v in eachvariable(equations)683mpi_mortars.u_upper[1, v, l, mortar] = u[v, l,684nnodes(dg),685element]686end687end688end689end690end691else # position == 3 -> current element is large692# Interpolate large element face data to small interface locations693if mpi_mortars.large_sides[mortar] == 1 # -> large element on left side694leftright = 1695if mpi_mortars.orientations[mortar] == 1696# L2 mortars in x-direction697u_large = view(u, :, nnodes(dg), :, element)698element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright,699mortar, u_large)700else701# L2 mortars in y-direction702u_large = view(u, :, :, nnodes(dg), element)703element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright,704mortar, u_large)705end706else # large_sides[mortar] == 2 -> large element on right side707leftright = 2708if mpi_mortars.orientations[mortar] == 1709# L2 mortars in x-direction710u_large = view(u, :, 1, :, element)711element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright,712mortar, u_large)713else714# L2 mortars in y-direction715u_large = view(u, :, :, 1, element)716element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright,717mortar, u_large)718end719end720end721end722end723724return nothing725end726727function calc_mpi_interface_flux!(surface_flux_values,728mesh::TreeMeshParallel{2},729have_nonconservative_terms::False, equations,730surface_integral, dg::DG, cache)731@unpack surface_flux = surface_integral732@unpack u, local_neighbor_ids, orientations, remote_sides = cache.mpi_interfaces733734@threaded for interface in eachmpiinterface(dg, cache)735# Get local neighboring element736element = local_neighbor_ids[interface]737738# Determine interface direction with respect to element:739if orientations[interface] == 1 # interface in x-direction740if remote_sides[interface] == 1 # local element in positive direction741direction = 1742else # local element in negative direction743direction = 2744end745else # interface in y-direction746if remote_sides[interface] == 1 # local element in positive direction747direction = 3748else # local element in negative direction749direction = 4750end751end752753for i in eachnode(dg)754# Call pointwise Riemann solver755u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface)756flux = surface_flux(u_ll, u_rr, orientations[interface], equations)757758# Copy flux to local element storage759for v in eachvariable(equations)760surface_flux_values[v, i, direction, element] = flux[v]761end762end763end764765return nothing766end767768function calc_mpi_mortar_flux!(surface_flux_values,769mesh::TreeMeshParallel{2},770have_nonconservative_terms::False, equations,771mortar_l2::LobattoLegendreMortarL2,772surface_integral, dg::DG, cache)773@unpack surface_flux = surface_integral774@unpack u_lower, u_upper, orientations = cache.mpi_mortars775@unpack fstar_primary_upper_threaded, fstar_primary_lower_threaded, fstar_secondary_upper_threaded, fstar_secondary_lower_threaded = cache776777@threaded for mortar in eachmpimortar(dg, cache)778# Choose thread-specific pre-allocated container779fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()]780fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()]781fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()]782fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()]783784# Because `have_nonconservative_terms` is `False` the primary and secondary fluxes785# are identical. So, we could possibly save on computation and just pass two copies later.786orientation = orientations[mortar]787calc_fstar!(fstar_primary_upper, equations, surface_flux, dg, u_upper, mortar,788orientation)789calc_fstar!(fstar_primary_lower, equations, surface_flux, dg, u_lower, mortar,790orientation)791calc_fstar!(fstar_secondary_upper, equations, surface_flux, dg, u_upper, mortar,792orientation)793calc_fstar!(fstar_secondary_lower, equations, surface_flux, dg, u_lower, mortar,794orientation)795796mpi_mortar_fluxes_to_elements!(surface_flux_values,797mesh, equations, mortar_l2, dg, cache,798mortar, fstar_primary_upper, fstar_primary_lower,799fstar_secondary_upper, fstar_secondary_lower)800end801802return nothing803end804805@inline function mpi_mortar_fluxes_to_elements!(surface_flux_values,806mesh::TreeMeshParallel{2}, equations,807mortar_l2::LobattoLegendreMortarL2,808dg::DGSEM, cache,809mortar, fstar_primary_upper,810fstar_primary_lower,811fstar_secondary_upper,812fstar_secondary_lower)813local_neighbor_ids = cache.mpi_mortars.local_neighbor_ids[mortar]814local_neighbor_positions = cache.mpi_mortars.local_neighbor_positions[mortar]815816for (element, position) in zip(local_neighbor_ids, local_neighbor_positions)817if position in (1, 2) # Current element is small818# Copy flux small to small819if cache.mpi_mortars.large_sides[mortar] == 1 # -> small elements on right side820if cache.mpi_mortars.orientations[mortar] == 1821# L2 mortars in x-direction822direction = 1823else824# L2 mortars in y-direction825direction = 3826end827else # large_sides[mortar] == 2 -> small elements on left side828if cache.mpi_mortars.orientations[mortar] == 1829# L2 mortars in x-direction830direction = 2831else832# L2 mortars in y-direction833direction = 4834end835end836837if position == 1838surface_flux_values[:, :, direction, element] .= fstar_primary_lower839elseif position == 2840surface_flux_values[:, :, direction, element] .= fstar_primary_upper841end842else # position == 3 -> current element is large843# Project small fluxes to large element844if cache.mpi_mortars.large_sides[mortar] == 1 # -> large element on left side845if cache.mpi_mortars.orientations[mortar] == 1846# L2 mortars in x-direction847direction = 2848else849# L2 mortars in y-direction850direction = 4851end852else # large_sides[mortar] == 2 -> large element on right side853if cache.mpi_mortars.orientations[mortar] == 1854# L2 mortars in x-direction855direction = 1856else857# L2 mortars in y-direction858direction = 3859end860end861862multiply_dimensionwise!(view(surface_flux_values, :, :, direction, element),863mortar_l2.reverse_upper, fstar_secondary_upper,864mortar_l2.reverse_lower, fstar_secondary_lower)865end866end867868return nothing869end870end # @muladd871872873