Path: blob/main/src/solvers/dgsem_p4est/dg_3d_parabolic.jl
5616 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67# This version is called during `calc_gradients!` and must be specialized because the8# MAGNITUDE of the flux does NOT depend on the outward normal.9# Thus, you don't need to scale by 2 (e.g., the scaling factor in the normals (and in the10# contravariant vectors) along large/small elements across a non-conforming11# interface in 2D) and flip the sign when storing the mortar fluxes back12# into `surface_flux_values`.13@inline function mortar_fluxes_to_elements_gradient!(surface_flux_values,14mesh::P4estMesh{3},15equations_parabolic::AbstractEquationsParabolic,16mortar_l2::LobattoLegendreMortarL2,17dg::DGSEM, cache, mortar,18fstar_primary,19fstar_secondary, u_buffer,20fstar_tmp)21@unpack neighbor_ids, node_indices = cache.mortars22index_range = eachnode(dg)23# Copy solution small to small24small_indices = node_indices[1, mortar]25small_direction = indices2direction(small_indices)2627for position in 1:4 # Loop over small elements28element = neighbor_ids[position, mortar]29for j in eachnode(dg), i in eachnode(dg)30for v in eachvariable(equations_parabolic)31surface_flux_values[v, i, j, small_direction, element] = fstar_primary[v,32i,33j,34position]35end36end37end3839# Project small fluxes to large element.40multiply_dimensionwise!(u_buffer,41mortar_l2.reverse_lower, mortar_l2.reverse_lower,42view(fstar_secondary, .., 1), fstar_tmp)43add_multiply_dimensionwise!(u_buffer,44mortar_l2.reverse_upper, mortar_l2.reverse_lower,45view(fstar_secondary, .., 2), fstar_tmp)46add_multiply_dimensionwise!(u_buffer,47mortar_l2.reverse_lower, mortar_l2.reverse_upper,48view(fstar_secondary, .., 3), fstar_tmp)49add_multiply_dimensionwise!(u_buffer,50mortar_l2.reverse_upper, mortar_l2.reverse_upper,51view(fstar_secondary, .., 4), fstar_tmp)5253# Copy interpolated flux values from buffer to large element face in the54# correct orientation.55# Note that the index of the small sides will always run forward but56# the index of the large side might need to run backwards for flipped sides.57large_element = neighbor_ids[5, mortar]58large_indices = node_indices[2, mortar]59large_direction = indices2direction(large_indices)60large_surface_indices = surface_indices(large_indices)6162i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_surface_indices[1],63index_range)64j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_surface_indices[2],65index_range)6667# Note that the indices of the small sides will always run forward but68# the large indices might need to run backwards for flipped sides.69i_large = i_large_start70j_large = j_large_start71for j in eachnode(dg)72for i in eachnode(dg)73for v in eachvariable(equations_parabolic)74surface_flux_values[v, i_large, j_large, large_direction, large_element] = u_buffer[v,75i,76j]77end78i_large += i_large_step_i79j_large += j_large_step_i80end81i_large += i_large_step_j82j_large += j_large_step_j83end8485return nothing86end8788function calc_interface_flux_gradient!(surface_flux_values,89mesh::P4estMesh{3},90equations_parabolic,91dg::DG, parabolic_scheme, cache)92@unpack neighbor_ids, node_indices = cache.interfaces93@unpack contravariant_vectors = cache.elements94index_range = eachnode(dg)9596@threaded for interface in eachinterface(dg, cache)97# Get element and side information on the primary element98primary_element = neighbor_ids[1, interface]99primary_indices = node_indices[1, interface]100primary_direction = indices2direction(primary_indices)101102i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1],103index_range)104j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2],105index_range)106k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3],107index_range)108109i_primary = i_primary_start110j_primary = j_primary_start111k_primary = k_primary_start112113# Get element and side information on the secondary element114secondary_element = neighbor_ids[2, interface]115secondary_indices = node_indices[2, interface]116secondary_direction = indices2direction(secondary_indices)117secondary_surface_indices = surface_indices(secondary_indices)118119# Get the surface indexing on the secondary element.120# Note that the indices of the primary side will always run forward but121# the secondary indices might need to run backwards for flipped sides.122i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[1],123index_range)124j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[2],125index_range)126i_secondary = i_secondary_start127j_secondary = j_secondary_start128129for j in eachnode(dg)130for i in eachnode(dg)131normal_direction = get_normal_direction(primary_direction,132contravariant_vectors,133i_primary, j_primary, k_primary,134primary_element)135136calc_interface_flux_gradient!(surface_flux_values, mesh,137equations_parabolic,138dg, parabolic_scheme, cache,139interface, normal_direction, i, j,140primary_direction, primary_element,141i_secondary, j_secondary,142secondary_direction, secondary_element)143144# Increment the primary element indices145i_primary += i_primary_step_i146j_primary += j_primary_step_i147k_primary += k_primary_step_i148# Increment the secondary element surface indices149i_secondary += i_secondary_step_i150j_secondary += j_secondary_step_i151end152# Increment the primary element indices153i_primary += i_primary_step_j154j_primary += j_primary_step_j155k_primary += k_primary_step_j156# Increment the secondary element surface indices157i_secondary += i_secondary_step_j158j_secondary += j_secondary_step_j159end160end161162return nothing163end164165# This version is used for parabolic gradient computations166@inline function calc_interface_flux_gradient!(surface_flux_values, mesh::P4estMesh{3},167equations_parabolic,168dg::DG, parabolic_scheme, cache,169interface_index, normal_direction,170primary_i_node_index,171primary_j_node_index,172primary_direction_index,173primary_element_index,174secondary_i_node_index,175secondary_j_node_index,176secondary_direction_index,177secondary_element_index)178@unpack u = cache.interfaces179180u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg,181primary_i_node_index, primary_j_node_index,182interface_index)183184flux_ = flux_parabolic(u_ll, u_rr, normal_direction, Gradient(),185equations_parabolic, parabolic_scheme)186187for v in eachvariable(equations_parabolic)188surface_flux_values[v, primary_i_node_index, primary_j_node_index, primary_direction_index, primary_element_index] = flux_[v]189# No sign flip required for gradient calculation because for parabolic terms,190# the normals are not embedded in `flux_` for gradient computations.191surface_flux_values[v, secondary_i_node_index, secondary_j_node_index, secondary_direction_index, secondary_element_index] = flux_[v]192end193194return nothing195end196197# This is the version used when calculating the divergence of the parabolic fluxes.198# Identical to weak-form volume integral/kernel for the purely hyperbolic case,199# except that the fluxes are here already precomputed in `calc_parabolic_fluxes!`200function calc_volume_integral!(du, flux_parabolic, mesh::P4estMesh{3},201equations_parabolic::AbstractEquationsParabolic,202dg::DGSEM, cache)203(; derivative_hat) = dg.basis204(; contravariant_vectors) = cache.elements205flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic206207@threaded for element in eachelement(dg, cache)208# Calculate volume terms in one element209for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)210flux1 = get_node_vars(flux_parabolic_x, equations_parabolic, dg,211i, j, k, element)212flux2 = get_node_vars(flux_parabolic_y, equations_parabolic, dg,213i, j, k, element)214flux3 = get_node_vars(flux_parabolic_z, equations_parabolic, dg,215i, j, k, element)216217# Compute the contravariant flux by taking the scalar product of the218# first contravariant vector Ja^1 and the flux vector219Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors,220i, j, k, element)221contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + Ja13 * flux3222for ii in eachnode(dg)223multiply_add_to_node_vars!(du, derivative_hat[ii, i],224contravariant_flux1,225equations_parabolic, dg, ii, j, k, element)226end227228# Compute the contravariant flux by taking the scalar product of the229# second contravariant vector Ja^2 and the flux vector230Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors,231i, j, k, element)232contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + Ja23 * flux3233for jj in eachnode(dg)234multiply_add_to_node_vars!(du, derivative_hat[jj, j],235contravariant_flux2,236equations_parabolic, dg, i, jj, k, element)237end238239# Compute the contravariant flux by taking the scalar product of the240# second contravariant vector Ja^2 and the flux vector241Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors,242i, j, k, element)243contravariant_flux3 = Ja31 * flux1 + Ja32 * flux2 + Ja33 * flux3244for kk in eachnode(dg)245multiply_add_to_node_vars!(du, derivative_hat[kk, k],246contravariant_flux3,247equations_parabolic, dg, i, j, kk, element)248end249end250end251252return nothing253end254255# This is the version used when calculating the divergence of the parabolic fluxes.256# Specialization `flux_parabolic::Tuple` needed to257# avoid amibiguity with the hyperbolic version of `prolong2interfaces!` in dg_3d.jl258# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.259function prolong2interfaces!(cache, flux_parabolic::Tuple,260mesh::P4estMesh{3},261equations_parabolic::AbstractEquationsParabolic,262dg::DG)263(; interfaces) = cache264(; contravariant_vectors) = cache.elements265index_range = eachnode(dg)266flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic267268@threaded for interface in eachinterface(dg, cache)269# Copy solution data from the primary element using "delayed indexing" with270# a start value and a step size to get the correct face and orientation.271# Note that in the current implementation, the interface will be272# "aligned at the primary element", i.e., the index of the primary side273# will always run forwards.274primary_element = interfaces.neighbor_ids[1, interface]275primary_indices = interfaces.node_indices[1, interface]276primary_direction = indices2direction(primary_indices)277278i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1],279index_range)280j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2],281index_range)282k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3],283index_range)284285i_primary = i_primary_start286j_primary = j_primary_start287k_primary = k_primary_start288289for j in eachnode(dg)290for i in eachnode(dg)291# this is the outward normal direction on the primary element292normal_direction = get_normal_direction(primary_direction,293contravariant_vectors,294i_primary, j_primary,295k_primary,296primary_element)297298for v in eachvariable(equations_parabolic)299# OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*!300flux_parabolic = SVector(flux_parabolic_x[v,301i_primary,302j_primary,303k_primary,304primary_element],305flux_parabolic_y[v,306i_primary,307j_primary,308k_primary,309primary_element],310flux_parabolic_z[v,311i_primary,312j_primary,313k_primary,314primary_element])315316interfaces.u[1, v, i, j, interface] = dot(flux_parabolic,317normal_direction)318end319i_primary += i_primary_step_i320j_primary += j_primary_step_i321k_primary += k_primary_step_i322end323i_primary += i_primary_step_j324j_primary += j_primary_step_j325k_primary += k_primary_step_j326end327328# Copy solution data from the secondary element using "delayed indexing" with329# a start value and a step size to get the correct face and orientation.330secondary_element = interfaces.neighbor_ids[2, interface]331secondary_indices = interfaces.node_indices[2, interface]332secondary_direction = indices2direction(secondary_indices)333334i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_indices[1],335index_range)336j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_indices[2],337index_range)338k_secondary_start, k_secondary_step_i, k_secondary_step_j = index_to_start_step_3d(secondary_indices[3],339index_range)340341i_secondary = i_secondary_start342j_secondary = j_secondary_start343k_secondary = k_secondary_start344for j in eachnode(dg)345for i in eachnode(dg)346# This is the outward normal direction on the secondary element.347# Here, we assume that normal_direction on the secondary element is348# the negative of normal_direction on the primary element.349normal_direction = get_normal_direction(secondary_direction,350contravariant_vectors,351i_secondary, j_secondary,352k_secondary,353secondary_element)354355for v in eachvariable(equations_parabolic)356# OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*!357flux_parabolic = SVector(flux_parabolic_x[v,358i_secondary,359j_secondary,360k_secondary,361secondary_element],362flux_parabolic_y[v,363i_secondary,364j_secondary,365k_secondary,366secondary_element],367flux_parabolic_z[v,368i_secondary,369j_secondary,370k_secondary,371secondary_element])372# store the normal flux with respect to the primary normal direction,373# which is the negative of the secondary normal direction374interfaces.u[2, v, i, j, interface] = -dot(flux_parabolic,375normal_direction)376end377i_secondary += i_secondary_step_i378j_secondary += j_secondary_step_i379k_secondary += k_secondary_step_i380end381i_secondary += i_secondary_step_j382j_secondary += j_secondary_step_j383k_secondary += k_secondary_step_j384end385end386387return nothing388end389390# This version is used for divergence flux computations391function calc_interface_flux!(surface_flux_values, mesh::P4estMesh{3},392equations_parabolic, dg::DG, parabolic_scheme,393cache)394(; neighbor_ids, node_indices) = cache.interfaces395(; contravariant_vectors) = cache.elements396index_range = eachnode(dg)397398@threaded for interface in eachinterface(dg, cache)399# Get element and side index information on the primary element400primary_element = neighbor_ids[1, interface]401primary_indices = node_indices[1, interface]402primary_direction_index = indices2direction(primary_indices)403404i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1],405index_range)406j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2],407index_range)408k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3],409index_range)410411i_primary = i_primary_start412j_primary = j_primary_start413k_primary = k_primary_start414415# Get element and side index information on the secondary element416secondary_element = neighbor_ids[2, interface]417secondary_indices = node_indices[2, interface]418secondary_direction_index = indices2direction(secondary_indices)419secondary_surface_indices = surface_indices(secondary_indices)420421# Initiate the secondary index to be used in the surface for loop.422# This index on the primary side will always run forward but423# the secondary index might need to run backwards for flipped sides.424# Get the surface indexing on the secondary element.425# Note that the indices of the primary side will always run forward but426# the secondary indices might need to run backwards for flipped sides.427i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[1],428index_range)429j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[2],430index_range)431i_secondary = i_secondary_start432j_secondary = j_secondary_start433434for j in eachnode(dg)435for i in eachnode(dg)436normal_direction = get_normal_direction(primary_direction_index,437contravariant_vectors,438i_primary, j_primary, k_primary,439primary_element)440# We prolong the parabolic flux dotted with respect the outward normal on the441# primary element.442parabolic_flux_normal_ll, parabolic_flux_normal_rr = get_surface_node_vars(cache.interfaces.u,443equations_parabolic,444dg,445i,446j,447interface)448449flux_ = flux_parabolic(parabolic_flux_normal_ll,450parabolic_flux_normal_rr,451normal_direction, Divergence(),452equations_parabolic, parabolic_scheme)453454for v in eachvariable(equations_parabolic)455surface_flux_values[v, i, j, primary_direction_index, primary_element] = flux_[v]456# Sign flip required for divergence calculation since the flux for the457# divergence involves the normal direction.458surface_flux_values[v, i_secondary, j_secondary, secondary_direction_index, secondary_element] = -flux_[v]459end460461# Increment the primary element indices462i_primary += i_primary_step_i463j_primary += j_primary_step_i464k_primary += k_primary_step_i465# Increment the secondary element surface indices466i_secondary += i_secondary_step_i467j_secondary += j_secondary_step_i468end469# Increment the primary element indices470i_primary += i_primary_step_j471j_primary += j_primary_step_j472k_primary += k_primary_step_j473# Increment the secondary element surface indices474i_secondary += i_secondary_step_j475j_secondary += j_secondary_step_j476end477end478479return nothing480end481482function prolong2mortars_divergence!(cache, flux_parabolic,483mesh::P4estMesh{3}, equations_parabolic,484mortar_l2::LobattoLegendreMortarL2,485dg::DGSEM)486@unpack neighbor_ids, node_indices = cache.mortars487@unpack fstar_tmp_threaded = cache488@unpack contravariant_vectors = cache.elements489index_range = eachnode(dg)490491flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic492493@threaded for mortar in eachmortar(dg, cache)494# Copy solution data from the small elements using "delayed indexing" with495# a start value and a step size to get the correct face and orientation.496small_indices = node_indices[1, mortar]497direction_index = indices2direction(small_indices)498499i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1],500index_range)501j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2],502index_range)503k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3],504index_range)505506for position in 1:4 # Loop over small elements507i_small = i_small_start508j_small = j_small_start509k_small = k_small_start510element = neighbor_ids[position, mortar]511for j in eachnode(dg)512for i in eachnode(dg)513normal_direction = get_normal_direction(direction_index,514contravariant_vectors,515i_small, j_small, k_small,516element)517518for v in eachvariable(equations_parabolic)519flux_parabolic = SVector(flux_parabolic_x[v, i_small, j_small,520k_small, element],521flux_parabolic_y[v, i_small, j_small,522k_small, element],523flux_parabolic_z[v, i_small, j_small,524k_small, element])525526cache.mortars.u[1, v, position, i, j, mortar] = dot(flux_parabolic,527normal_direction)528end529i_small += i_small_step_i530j_small += j_small_step_i531k_small += k_small_step_i532end533i_small += i_small_step_j534j_small += j_small_step_j535k_small += k_small_step_j536end537end538539# Buffer to copy solution values of the large element in the correct orientation540# before interpolating541u_buffer = cache.u_threaded[Threads.threadid()]542543# temporary buffer for projections544fstar_tmp = fstar_tmp_threaded[Threads.threadid()]545546# Copy solution of large element face to buffer in the547# correct orientation548large_indices = node_indices[2, mortar]549550i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_indices[1],551index_range)552j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_indices[2],553index_range)554k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d(large_indices[3],555index_range)556557i_large = i_large_start558j_large = j_large_start559k_large = k_large_start560element = neighbor_ids[5, mortar] # Large element561for j in eachnode(dg)562for i in eachnode(dg)563normal_direction = get_normal_direction(direction_index,564contravariant_vectors,565i_large, j_large, k_large,566element)567568for v in eachvariable(equations_parabolic)569flux_parabolic = SVector(flux_parabolic_x[v,570i_large,571j_large,572k_large,573element],574flux_parabolic_y[v,575i_large,576j_large,577k_large,578element],579flux_parabolic_z[v,580i_large,581j_large,582k_large,583element])584585# We prolong the parabolic flux dotted with respect the outward normal586# on the small element. We scale by -1/2 here because the normal587# direction on the large element is negative 2x that of the small588# element (these normal directions are "scaled" by the surface Jacobian)589u_buffer[v, i, j] = -0.5f0 * dot(flux_parabolic, normal_direction)590end591i_large += i_large_step_i592j_large += j_large_step_i593k_large += k_large_step_i594end595i_large += i_large_step_j596j_large += j_large_step_j597k_large += k_large_step_j598end599600# Interpolate large element face data from buffer to small face locations601multiply_dimensionwise!(view(cache.mortars.u, 2, :, 1, :, :, mortar),602mortar_l2.forward_lower,603mortar_l2.forward_lower,604u_buffer, fstar_tmp)605multiply_dimensionwise!(view(cache.mortars.u, 2, :, 2, :, :, mortar),606mortar_l2.forward_upper,607mortar_l2.forward_lower,608u_buffer, fstar_tmp)609multiply_dimensionwise!(view(cache.mortars.u, 2, :, 3, :, :, mortar),610mortar_l2.forward_lower,611mortar_l2.forward_upper,612u_buffer, fstar_tmp)613multiply_dimensionwise!(view(cache.mortars.u, 2, :, 4, :, :, mortar),614mortar_l2.forward_upper,615mortar_l2.forward_upper,616u_buffer, fstar_tmp)617end618619return nothing620end621622function calc_mortar_flux_divergence!(surface_flux_values,623mesh::P4estMesh{3},624equations_parabolic,625mortar_l2::LobattoLegendreMortarL2,626dg::DG, parabolic_scheme, cache)627@unpack neighbor_ids, node_indices = cache.mortars628@unpack contravariant_vectors = cache.elements629@unpack fstar_primary_threaded, fstar_tmp_threaded = cache630index_range = eachnode(dg)631632@threaded for mortar in eachmortar(dg, cache)633# Choose thread-specific pre-allocated container634fstar = fstar_primary_threaded[Threads.threadid()]635fstar_tmp = fstar_tmp_threaded[Threads.threadid()]636637# Get index information on the small elements638small_indices = node_indices[1, mortar]639small_direction = indices2direction(small_indices)640641i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1],642index_range)643j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2],644index_range)645k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3],646index_range)647648for position in 1:4 # Loop over small elements649i_small = i_small_start650j_small = j_small_start651k_small = k_small_start652element = neighbor_ids[position, mortar]653for j in eachnode(dg)654for i in eachnode(dg)655normal_direction = get_normal_direction(small_direction,656contravariant_vectors,657i_small, j_small, k_small,658element)659660for v in eachvariable(equations_parabolic)661parabolic_flux_normal_ll = cache.mortars.u[1, v, position, i, j,662mortar]663parabolic_flux_normal_rr = cache.mortars.u[2, v, position, i, j,664mortar]665666flux_ = flux_parabolic(parabolic_flux_normal_ll,667parabolic_flux_normal_rr,668normal_direction, Divergence(),669equations_parabolic, parabolic_scheme)670671# Sign flip (and scaling by 0.5) already handled above in `prolong2mortars_divergence!`672fstar[v, i, j, position] = flux_673end674675i_small += i_small_step_i676j_small += j_small_step_i677k_small += k_small_step_i678end679i_small += i_small_step_j680j_small += j_small_step_j681k_small += k_small_step_j682end683end684685# Buffer to interpolate flux values of the large element to before686# copying in the correct orientation687u_buffer = cache.u_threaded[Threads.threadid()]688689# this reuses the hyperbolic version of `mortar_fluxes_to_elements!`690mortar_fluxes_to_elements!(surface_flux_values, mesh,691equations_parabolic, mortar_l2, dg, cache,692mortar, fstar, fstar, u_buffer,693fstar_tmp)694end695696return nothing697end698699function calc_mortar_flux_gradient!(surface_flux_values,700mesh::P4estMesh{3}, equations_parabolic,701mortar_l2::LobattoLegendreMortarL2,702dg::DG, parabolic_scheme, cache)703@unpack neighbor_ids, node_indices = cache.mortars704@unpack contravariant_vectors = cache.elements705@unpack fstar_primary_threaded, fstar_secondary_threaded, fstar_tmp_threaded = cache706index_range = eachnode(dg)707708@threaded for mortar in eachmortar(dg, cache)709# Choose thread-specific pre-allocated container710fstar_primary = fstar_primary_threaded[Threads.threadid()]711fstar_secondary = fstar_secondary_threaded[Threads.threadid()]712fstar_tmp = fstar_tmp_threaded[Threads.threadid()]713714# Get index information on the small elements715small_indices = node_indices[1, mortar]716small_direction = indices2direction(small_indices)717718i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1],719index_range)720j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2],721index_range)722k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3],723index_range)724725for position in 1:4726i_small = i_small_start727j_small = j_small_start728k_small = k_small_start729element = neighbor_ids[position, mortar]730for j in eachnode(dg)731for i in eachnode(dg)732normal_direction = get_normal_direction(small_direction,733contravariant_vectors,734i_small, j_small, k_small,735element)736737calc_mortar_flux_gradient!(fstar_primary, fstar_secondary,738mesh, equations_parabolic,739dg, parabolic_scheme, cache,740mortar, position, normal_direction,741i, j)742743i_small += i_small_step_i744j_small += j_small_step_i745k_small += k_small_step_i746end747i_small += i_small_step_j748j_small += j_small_step_j749k_small += k_small_step_j750end751end752753# Buffer to interpolate flux values of the large element to before754# copying in the correct orientation755u_buffer = cache.u_threaded[Threads.threadid()]756757# in calc_interface_flux!, the interface flux is computed once over each758# interface using the normal from the "primary" element. The result is then759# passed back to the "secondary" element, flipping the sign to account for the760# change in the normal direction. For mortars, this sign flip occurs in761# "mortar_fluxes_to_elements_gradient!" instead.762mortar_fluxes_to_elements_gradient!(surface_flux_values,763mesh, equations_parabolic, mortar_l2,764dg, cache,765mortar, fstar_primary, fstar_secondary,766u_buffer, fstar_tmp)767end768769return nothing770end771772# We structure `calc_mortar_flux_gradient!` similarly to "calc_mortar_flux!" for773# hyperbolic equations with no nonconservative terms.774# The reasoning is that parabolic fluxes are treated like conservative775# terms (e.g., we compute a parabolic conservative "flux") and thus no776# non-conservative terms are present.777@inline function calc_mortar_flux_gradient!(fstar_primary, fstar_secondary,778mesh::P4estMesh{3},779equations_parabolic,780dg::DG, parabolic_scheme, cache,781mortar_index, position_index,782normal_direction,783i_node_index, j_node_index)784@unpack u = cache.mortars785786u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, position_index,787i_node_index, j_node_index, mortar_index)788789flux_ = flux_parabolic(u_ll, u_rr, normal_direction, Gradient(),790equations_parabolic, parabolic_scheme)791792# Copy flux to buffer793set_node_vars!(fstar_primary, flux_, equations_parabolic, dg,794i_node_index, j_node_index, position_index)795# As in `calc_interface_flux_gradient!`, no sign flip is necessary for the fluxes used in gradient calculations796set_node_vars!(fstar_secondary, flux_, equations_parabolic, dg,797i_node_index, j_node_index, position_index)798799return nothing800end801802function calc_volume_integral_gradient!(gradients, u_transformed,803mesh::P4estMesh{3}, # for dispatch only804equations_parabolic::AbstractEquationsParabolic,805dg::DG, cache)806@unpack derivative_hat = dg.basis807@unpack contravariant_vectors = cache.elements808gradients_x, gradients_y, gradients_z = gradients809810@threaded for element in eachelement(dg, cache)811# Calculate volume terms in one element,812# corresponds to `kernel` functions for the hyperbolic part of the flux813for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)814u_node = get_node_vars(u_transformed, equations_parabolic, dg,815i, j, k, element)816817for ii in eachnode(dg)818multiply_add_to_node_vars!(gradients_x, derivative_hat[ii, i],819u_node, equations_parabolic, dg,820ii, j, k, element)821end822823for jj in eachnode(dg)824multiply_add_to_node_vars!(gradients_y, derivative_hat[jj, j],825u_node, equations_parabolic, dg,826i, jj, k, element)827end828829for kk in eachnode(dg)830multiply_add_to_node_vars!(gradients_z, derivative_hat[kk, k],831u_node, equations_parabolic, dg,832i, j, kk, element)833end834end835836# now that the reference coordinate gradients are computed, transform them node-by-node to physical gradients837# using the contravariant vectors838for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)839Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors,840i, j, k, element)841Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors,842i, j, k, element)843Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors,844i, j, k, element)845846gradients_reference_1 = get_node_vars(gradients_x, equations_parabolic, dg,847i, j, k, element)848gradients_reference_2 = get_node_vars(gradients_y, equations_parabolic, dg,849i, j, k, element)850gradients_reference_3 = get_node_vars(gradients_z, equations_parabolic, dg,851i, j, k, element)852853# note that the contravariant vectors are transposed compared with computations of flux854# divergences in `calc_volume_integral!`. See855# https://github.com/trixi-framework/Trixi.jl/pull/1490#discussion_r1213345190856# for a more detailed discussion.857gradient_x_node = Ja11 * gradients_reference_1 +858Ja21 * gradients_reference_2 +859Ja31 * gradients_reference_3860gradient_y_node = Ja12 * gradients_reference_1 +861Ja22 * gradients_reference_2 +862Ja32 * gradients_reference_3863gradient_z_node = Ja13 * gradients_reference_1 +864Ja23 * gradients_reference_2 +865Ja33 * gradients_reference_3866867set_node_vars!(gradients_x, gradient_x_node, equations_parabolic, dg,868i, j, k, element)869set_node_vars!(gradients_y, gradient_y_node, equations_parabolic, dg,870i, j, k, element)871set_node_vars!(gradients_z, gradient_z_node, equations_parabolic, dg,872i, j, k, element)873end874end875876return nothing877end878879# Specialization `flux_parabolic::Tuple` needed to880# avoid amibiguity with the hyperbolic version of `prolong2boundaries!` in dg_3d.jl881# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.882function prolong2boundaries!(cache, flux_parabolic::Tuple,883mesh::P4estMesh{3},884equations_parabolic::AbstractEquationsParabolic,885dg::DG)886(; boundaries) = cache887(; contravariant_vectors) = cache.elements888index_range = eachnode(dg)889890flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic891892@threaded for boundary in eachboundary(dg, cache)893# Copy solution data from the element using "delayed indexing" with894# a start value and a step size to get the correct face and orientation.895element = boundaries.neighbor_ids[boundary]896node_indices = boundaries.node_indices[boundary]897direction = indices2direction(node_indices)898899i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1],900index_range)901j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2],902index_range)903k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3],904index_range)905906i_node = i_node_start907j_node = j_node_start908k_node = k_node_start909910for j in eachnode(dg)911for i in eachnode(dg)912# this is the outward normal direction on the primary element913normal_direction = get_normal_direction(direction,914contravariant_vectors,915i_node, j_node, k_node, element)916917for v in eachvariable(equations_parabolic)918flux_parabolic = SVector(flux_parabolic_x[v, i_node, j_node, k_node,919element],920flux_parabolic_y[v, i_node, j_node, k_node,921element],922flux_parabolic_z[v, i_node, j_node, k_node,923element])924925boundaries.u[v, i, j, boundary] = dot(flux_parabolic,926normal_direction)927end928i_node += i_node_step_i929j_node += j_node_step_i930k_node += k_node_step_i931end932i_node += i_node_step_j933j_node += j_node_step_j934k_node += k_node_step_j935end936end937return nothing938end939940function calc_boundary_flux!(cache, t,941boundary_condition_parabolic, # works with Dict types942boundary_condition_indices,943operator_type, mesh::P4estMesh{3},944equations_parabolic::AbstractEquationsParabolic,945surface_integral, dg::DG)946(; boundaries) = cache947(; node_coordinates, surface_flux_values) = cache.elements948(; contravariant_vectors) = cache.elements949index_range = eachnode(dg)950951@threaded for local_index in eachindex(boundary_condition_indices)952# Use the local index to get the global boundary index from the pre-sorted list953boundary_index = boundary_condition_indices[local_index]954955# Get information on the adjacent element, compute the surface fluxes,956# and store them957element = boundaries.neighbor_ids[boundary_index]958node_indices = boundaries.node_indices[boundary_index]959direction_index = indices2direction(node_indices)960961i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1],962index_range)963j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2],964index_range)965k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3],966index_range)967968i_node = i_node_start969j_node = j_node_start970k_node = k_node_start971972for j in eachnode(dg)973for i in eachnode(dg)974# Extract solution data from boundary container975u_inner = get_node_vars(boundaries.u, equations_parabolic, dg, i, j,976boundary_index)977978# Outward-pointing normal direction (not normalized)979normal_direction = get_normal_direction(direction_index,980contravariant_vectors,981i_node, j_node, k_node, element)982983# TODO: revisit if we want more general boundary treatments.984# This assumes the gradient numerical flux at the boundary is the gradient variable,985# which is consistent with BR1, LDG.986flux_inner = u_inner987988# Coordinates at boundary node989x = get_node_coords(node_coordinates, equations_parabolic, dg,990i_node, j_node, k_node, element)991992flux_ = boundary_condition_parabolic(flux_inner, u_inner,993normal_direction,994x, t, operator_type,995equations_parabolic)996997# Copy flux to element storage in the correct orientation998for v in eachvariable(equations_parabolic)999surface_flux_values[v, i, j, direction_index, element] = flux_[v]1000end10011002i_node += i_node_step_i1003j_node += j_node_step_i1004k_node += k_node_step_i1005end1006i_node += i_node_step_j1007j_node += j_node_step_j1008k_node += k_node_step_j1009end1010end10111012return nothing1013end10141015function calc_surface_integral_gradient!(gradients,1016mesh::P4estMesh{3}, # for dispatch only1017equations_parabolic::AbstractEquationsParabolic,1018dg::DGSEM, cache)1019@unpack inverse_weights = dg.basis1020@unpack surface_flux_values = cache.elements1021@unpack contravariant_vectors = cache.elements10221023# We also use explicit assignments instead of `+=` to let `@muladd` turn these1024# into FMAs (see comment at the top of the file).1025factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±11026@threaded for element in eachelement(dg, cache)1027for l in eachnode(dg), m in eachnode(dg)1028for v in eachvariable(equations_parabolic)1029for dim in 1:31030grad = gradients[dim]1031# surface at -x1032normal_direction = get_normal_direction(1, contravariant_vectors,10331, l, m,1034element)1035grad[v, 1, l, m, element] = (grad[v,10361, l, m,1037element] +1038surface_flux_values[v,1039l, m, 1,1040element] *1041factor *1042normal_direction[dim])10431044# surface at +x1045normal_direction = get_normal_direction(2, contravariant_vectors,1046nnodes(dg), l, m,1047element)1048grad[v, nnodes(dg), l, m, element] = (grad[v,1049nnodes(dg), l, m,1050element] +1051surface_flux_values[v,1052l, m, 2,1053element] *1054factor *1055normal_direction[dim])10561057# surface at -y1058normal_direction = get_normal_direction(3, contravariant_vectors,1059l, 1, m,1060element)1061grad[v, l, 1, m, element] = (grad[v,1062l, 1, m,1063element] +1064surface_flux_values[v,1065l, m, 3,1066element] *1067factor *1068normal_direction[dim])10691070# surface at +y1071normal_direction = get_normal_direction(4, contravariant_vectors,1072l, nnodes(dg), m,1073element)1074grad[v, l, nnodes(dg), m, element] = (grad[v,1075l, nnodes(dg), m,1076element] +1077surface_flux_values[v,1078l, m, 4,1079element] *1080factor *1081normal_direction[dim])10821083# surface at -z1084normal_direction = get_normal_direction(5, contravariant_vectors,1085l, m, 1,1086element)1087grad[v, l, m, 1, element] = (grad[v,1088l, m, 1,1089element] +1090surface_flux_values[v,1091l, m, 5,1092element] *1093factor *1094normal_direction[dim])10951096# surface at +z1097normal_direction = get_normal_direction(6, contravariant_vectors,1098l, m, nnodes(dg),1099element)1100grad[v, l, m, nnodes(dg), element] = (grad[v,1101l, m, nnodes(dg),1102element] +1103surface_flux_values[v,1104l, m, 6,1105element] *1106factor *1107normal_direction[dim])1108end1109end1110end1111end11121113return nothing1114end11151116# Needed to *not* flip the sign of the inverse Jacobian.1117# This is because the parabolic fluxes are assumed to be of the form1118# `du/dt + df/dx = dg/dx + source(x,t)`,1119# where f(u) is the inviscid flux and g(u) is the parabolic flux.1120function apply_jacobian_parabolic!(du::AbstractArray, mesh::P4estMesh{3},1121equations_parabolic::AbstractEquationsParabolic,1122dg::DG, cache)1123@unpack inverse_jacobian = cache.elements11241125@threaded for element in eachelement(dg, cache)1126for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)1127factor = inverse_jacobian[i, j, k, element]11281129for v in eachvariable(equations_parabolic)1130du[v, i, j, k, element] *= factor1131end1132end1133end11341135return nothing1136end1137end # @muladd113811391140