Path: blob/main/src/solvers/dgsem_p4est/dg_2d_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#=8Reusing `rhs_parabolic!` for `P4estMesh`es is not easily possible as9for `P4estMesh`es we call1011```12prolong2mortars_divergence!(cache, flux_parabolic, mesh, equations_parabolic,13dg.mortar, dg)1415calc_mortar_flux_divergence!(cache_parabolic.elements.surface_flux_values,16mesh, equations_parabolic, dg.mortar, dg, cache)17```18instead of19```20prolong2mortars!(cache, flux_parabolic, mesh, equations_parabolic,21dg.mortar, dg)2223calc_mortar_flux!(cache_parabolic.elements.surface_flux_values,24mesh, equations_parabolic, dg.mortar, dg, cache)25```26which works on `TreeMesh`es.27=#28function rhs_parabolic!(du, u, t, mesh::Union{P4estMesh{2}, P4estMesh{3}},29equations_parabolic::AbstractEquationsParabolic,30boundary_conditions_parabolic, source_terms_parabolic,31dg::DG, parabolic_scheme, cache, cache_parabolic)32@unpack parabolic_container = cache_parabolic33@unpack u_transformed, gradients, flux_parabolic = parabolic_container3435# Convert conservative variables to a form more suitable for parabolic flux calculations36@trixi_timeit timer() "transform variables" begin37transform_variables!(u_transformed, u, mesh, equations_parabolic,38dg, cache)39end4041# Compute the gradients of the transformed variables42@trixi_timeit timer() "calculate gradient" begin43calc_gradient!(gradients, u_transformed, t, mesh,44equations_parabolic, boundary_conditions_parabolic,45dg, parabolic_scheme, cache)46end4748# Compute and store the parabolic fluxes49@trixi_timeit timer() "calculate parabolic fluxes" begin50calc_parabolic_fluxes!(flux_parabolic, gradients, u_transformed, mesh,51equations_parabolic, dg, cache)52end5354# The remainder of this function is essentially a regular rhs! for parabolic55# equations (i.e., it computes the divergence of the parabolic fluxes)56#57# OBS! In `calc_parabolic_fluxes!`, the parabolic flux values at the volume nodes of each element have58# been computed and stored in `flux_parabolic`. In the following, we *reuse* (abuse) the59# `interfaces` and `boundaries` containers in `cache` to interpolate and store the60# *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it61# is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *parabolic flux values*62# and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we63# do not need to recreate the existing data structure only with a different name, and c) we do not64# need to interpolate solutions *and* gradients to the surfaces.6566# Reset du67@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)6869# Calculate volume integral70# This calls the specialized version for the parabolic fluxes from71# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.72@trixi_timeit timer() "volume integral" begin73calc_volume_integral!(du, flux_parabolic, mesh, equations_parabolic, dg, cache)74end7576# Prolong solution to interfaces.77# This calls the specialized version for the parabolic fluxes from78# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.79@trixi_timeit timer() "prolong2interfaces" begin80prolong2interfaces!(cache, flux_parabolic, mesh, equations_parabolic, dg)81end8283# Calculate interface fluxes84# This calls the specialized version for the parabolic fluxes from85# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.86@trixi_timeit timer() "interface flux" begin87calc_interface_flux!(cache.elements.surface_flux_values, mesh,88equations_parabolic, dg, parabolic_scheme, cache)89end9091# Prolong parabolic fluxes to boundaries.92# This calls the specialized version for the parabolic fluxes from93# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.94@trixi_timeit timer() "prolong2boundaries" begin95prolong2boundaries!(cache, flux_parabolic, mesh, equations_parabolic, dg)96end9798# Calculate boundary fluxes.99# This calls the specialized version for parabolic equations.100@trixi_timeit timer() "boundary flux" begin101calc_boundary_flux_divergence!(cache, t,102boundary_conditions_parabolic, mesh,103equations_parabolic,104dg.surface_integral, dg)105end106107# Prolong solution to mortars.108# This calls the specialized version for parabolic equations.109@trixi_timeit timer() "prolong2mortars" begin110prolong2mortars_divergence!(cache, flux_parabolic, mesh, equations_parabolic,111dg.mortar, dg)112end113114# Calculate mortar fluxes.115# This calls the specialized version for parabolic equations.116@trixi_timeit timer() "mortar flux" begin117calc_mortar_flux_divergence!(cache.elements.surface_flux_values,118mesh, equations_parabolic, dg.mortar,119dg, parabolic_scheme, cache)120end121122# Calculate surface integrals.123# This reuses `calc_surface_integral!` for the purely hyperbolic case.124@trixi_timeit timer() "surface integral" begin125calc_surface_integral!(nothing, du, u, mesh, equations_parabolic,126dg.surface_integral, dg, cache)127end128129# Apply Jacobian from mapping to reference element130@trixi_timeit timer() "Jacobian" begin131apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache)132end133134@trixi_timeit timer() "source terms parabolic" begin135calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,136equations_parabolic, dg, cache)137end138139return nothing140end141142function calc_gradient!(gradients, u_transformed, t,143mesh::Union{P4estMesh{2}, P4estMesh{3}},144equations_parabolic, boundary_conditions_parabolic,145dg::DG, parabolic_scheme, cache)146# Reset gradients147@trixi_timeit timer() "reset gradients" begin148reset_gradients!(gradients, dg, cache)149end150151# Calculate volume integral152@trixi_timeit timer() "volume integral" begin153calc_volume_integral_gradient!(gradients, u_transformed,154mesh, equations_parabolic, dg, cache)155end156157# Prolong solution to interfaces.158# This reuses `prolong2interfaces` for the purely hyperbolic case.159@trixi_timeit timer() "prolong2interfaces" begin160prolong2interfaces!(nothing, cache, u_transformed, mesh,161equations_parabolic, dg)162end163164# Calculate interface fluxes for the gradients165@trixi_timeit timer() "interface flux" begin166@unpack surface_flux_values = cache.elements167calc_interface_flux_gradient!(surface_flux_values, mesh, equations_parabolic,168dg, parabolic_scheme, cache)169end170171# Prolong solution to boundaries.172# This reuses `prolong2boundaries` for the purely hyperbolic case.173@trixi_timeit timer() "prolong2boundaries" begin174prolong2boundaries!(cache, u_transformed, mesh,175equations_parabolic, dg)176end177178# Calculate boundary fluxes179@trixi_timeit timer() "boundary flux" begin180calc_boundary_flux_gradient!(cache, t, boundary_conditions_parabolic,181mesh, equations_parabolic, dg.surface_integral,182dg)183end184185# Prolong solution to mortars.186# This reuses `prolong2mortars` for the purely hyperbolic case.187@trixi_timeit timer() "prolong2mortars" begin188prolong2mortars!(cache, u_transformed, mesh, equations_parabolic,189dg.mortar, dg)190end191192# Calculate mortar fluxes193@trixi_timeit timer() "mortar flux" begin194calc_mortar_flux_gradient!(cache.elements.surface_flux_values,195mesh, equations_parabolic, dg.mortar,196dg, parabolic_scheme, cache)197end198199# Calculate surface integrals200@trixi_timeit timer() "surface integral" begin201calc_surface_integral_gradient!(gradients, mesh, equations_parabolic,202dg, cache)203end204205# Apply Jacobian from mapping to reference element206@trixi_timeit timer() "Jacobian" begin207apply_jacobian_parabolic!(gradients, mesh, equations_parabolic, dg,208cache)209end210211return nothing212end213214# This version is called during `calc_gradients!` and must be specialized because the215# MAGNITUDE of the flux does NOT depend on the outward normal.216# Thus, you don't need to scale by 2 (e.g., the scaling factor in the normals (and in the217# contravariant vectors) along large/small elements across a non-conforming218# interface in 2D) and flip the sign when storing the mortar fluxes back219# into `surface_flux_values`.220@inline function mortar_fluxes_to_elements_gradient!(surface_flux_values,221mesh::P4estMesh{2},222equations_parabolic::AbstractEquationsParabolic,223mortar_l2::LobattoLegendreMortarL2,224dg::DGSEM, cache, mortar,225fstar_primary, fstar_secondary,226u_buffer)227@unpack neighbor_ids, node_indices = cache.mortars228# Copy solution small to small229small_indices = node_indices[1, mortar]230small_direction = indices2direction(small_indices)231232for position in 1:2233element = neighbor_ids[position, mortar]234for i in eachnode(dg)235for v in eachvariable(equations_parabolic)236surface_flux_values[v, i, small_direction, element] = fstar_primary[position][v,237i]238end239end240end241242# Project small fluxes to large element.243multiply_dimensionwise!(u_buffer,244mortar_l2.reverse_upper, fstar_secondary[2],245mortar_l2.reverse_lower, fstar_secondary[1])246247# Copy interpolated flux values from buffer to large element face in the248# correct orientation.249# Note that the index of the small sides will always run forward but250# the index of the large side might need to run backwards for flipped sides.251large_element = neighbor_ids[3, mortar]252large_indices = node_indices[2, mortar]253large_direction = indices2direction(large_indices)254255if :i_backward in large_indices256for i in eachnode(dg)257for v in eachvariable(equations_parabolic)258surface_flux_values[v, end + 1 - i, large_direction, large_element] = u_buffer[v,259i]260end261end262else263for i in eachnode(dg)264for v in eachvariable(equations_parabolic)265surface_flux_values[v, i, large_direction, large_element] = u_buffer[v,266i]267end268end269end270271return nothing272end273274function calc_interface_flux_gradient!(surface_flux_values,275mesh::Union{P4estMesh{2}, P4estMeshView{2}},276equations_parabolic,277dg::DG, parabolic_scheme, cache)278@unpack neighbor_ids, node_indices = cache.interfaces279@unpack contravariant_vectors = cache.elements280index_range = eachnode(dg)281index_end = last(index_range)282283@threaded for interface in eachinterface(dg, cache)284# Get element and side index information on the primary element285primary_element = neighbor_ids[1, interface]286primary_indices = node_indices[1, interface]287primary_direction = indices2direction(primary_indices)288289# Create the local i,j indexing on the primary element used to pull normal direction information290i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1],291index_range)292j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2],293index_range)294295i_primary = i_primary_start296j_primary = j_primary_start297298# Get element and side index information on the secondary element299secondary_element = neighbor_ids[2, interface]300secondary_indices = node_indices[2, interface]301secondary_direction = indices2direction(secondary_indices)302303# Initiate the secondary index to be used in the surface for loop.304# This index on the primary side will always run forward but305# the secondary index might need to run backwards for flipped sides.306if :i_backward in secondary_indices307node_secondary = index_end308node_secondary_step = -1309else310node_secondary = 1311node_secondary_step = 1312end313314for i in eachnode(dg)315normal_direction = get_normal_direction(primary_direction,316contravariant_vectors,317i_primary, j_primary,318primary_element)319320calc_interface_flux_gradient!(surface_flux_values, mesh,321equations_parabolic,322dg, parabolic_scheme, cache,323interface, normal_direction, i,324primary_direction, primary_element,325node_secondary,326secondary_direction, secondary_element)327328# Increment primary element indices to pull the normal direction329i_primary += i_primary_step330j_primary += j_primary_step331# Increment the surface node index along the secondary element332node_secondary += node_secondary_step333end334end335336return nothing337end338339# This is the version used when calculating the gradient of the parabolic fluxes (called from above)340@inline function calc_interface_flux_gradient!(surface_flux_values, mesh::P4estMesh{2},341equations_parabolic,342dg::DG, parabolic_scheme, cache,343interface_index, normal_direction,344primary_node_index,345primary_direction_index,346primary_element_index,347secondary_node_index,348secondary_direction_index,349secondary_element_index)350@unpack u = cache.interfaces351352u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, primary_node_index,353interface_index)354355flux_ = flux_parabolic(u_ll, u_rr, normal_direction, Gradient(),356equations_parabolic, parabolic_scheme)357358for v in eachvariable(equations_parabolic)359surface_flux_values[v, primary_node_index, primary_direction_index, primary_element_index] = flux_[v]360# No sign flip required for gradient calculation because for parabolic terms,361# the normals are not embedded in `flux_` for gradient computations.362surface_flux_values[v, secondary_node_index, secondary_direction_index, secondary_element_index] = flux_[v]363end364365return nothing366end367368# This is the version used when calculating the divergence of the parabolic fluxes.369# Identical to weak-form volume integral/kernel for the purely hyperbolic case,370# except that the fluxes are here already precomputed in `calc_parabolic_fluxes!`371function calc_volume_integral!(du, flux_parabolic, mesh::P4estMesh{2},372equations_parabolic::AbstractEquationsParabolic,373dg::DGSEM, cache)374(; derivative_hat) = dg.basis375(; contravariant_vectors) = cache.elements376flux_parabolic_x, flux_parabolic_y = flux_parabolic377378@threaded for element in eachelement(dg, cache)379# Calculate volume terms in one element380for j in eachnode(dg), i in eachnode(dg)381flux1 = get_node_vars(flux_parabolic_x, equations_parabolic, dg,382i, j, element)383flux2 = get_node_vars(flux_parabolic_y, equations_parabolic, dg,384i, j, element)385386# Compute the contravariant flux by taking the scalar product of the387# first contravariant vector Ja^1 and the flux vector388Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors,389i, j, element)390contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2391for ii in eachnode(dg)392multiply_add_to_node_vars!(du, derivative_hat[ii, i],393contravariant_flux1,394equations_parabolic, dg, ii, j, element)395end396397# Compute the contravariant flux by taking the scalar product of the398# second contravariant vector Ja^2 and the flux vector399Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors,400i, j, element)401contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2402for jj in eachnode(dg)403multiply_add_to_node_vars!(du, derivative_hat[jj, j],404contravariant_flux2,405equations_parabolic, dg, i, jj, element)406end407end408end409410return nothing411end412413# This is the version used when calculating the divergence of the parabolic fluxes.414# Specialization `flux_parabolic::Tuple` needed to415# avoid amibiguity with the hyperbolic version of `prolong2interfaces!` in dg_2d.jl416# which is for the variables itself, i.e., `u::Array{uEltype, 4}`.417function prolong2interfaces!(cache, flux_parabolic::Tuple,418mesh::Union{P4estMesh{2}, P4estMeshView{2}},419equations_parabolic::AbstractEquationsParabolic,420dg::DG)421(; interfaces) = cache422(; contravariant_vectors) = cache.elements423index_range = eachnode(dg)424flux_parabolic_x, flux_parabolic_y = flux_parabolic425426@threaded for interface in eachinterface(dg, cache)427# Copy solution data from the primary element using "delayed indexing" with428# a start value and a step size to get the correct face and orientation.429# Note that in the current implementation, the interface will be430# "aligned at the primary element", i.e., the index of the primary side431# will always run forwards.432primary_element = interfaces.neighbor_ids[1, interface]433primary_indices = interfaces.node_indices[1, interface]434primary_direction = indices2direction(primary_indices)435436i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1],437index_range)438j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2],439index_range)440441i_primary = i_primary_start442j_primary = j_primary_start443for i in eachnode(dg)444# this is the outward normal direction on the primary element445normal_direction = get_normal_direction(primary_direction,446contravariant_vectors,447i_primary, j_primary,448primary_element)449450for v in eachvariable(equations_parabolic)451# OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*!452flux_parabolic = SVector(flux_parabolic_x[v, i_primary, j_primary,453primary_element],454flux_parabolic_y[v, i_primary, j_primary,455primary_element])456457interfaces.u[1, v, i, interface] = dot(flux_parabolic, normal_direction)458end459i_primary += i_primary_step460j_primary += j_primary_step461end462463# Copy solution data from the secondary element using "delayed indexing" with464# a start value and a step size to get the correct face and orientation.465secondary_element = interfaces.neighbor_ids[2, interface]466secondary_indices = interfaces.node_indices[2, interface]467secondary_direction = indices2direction(secondary_indices)468469i_secondary_start, i_secondary_step = index_to_start_step_2d(secondary_indices[1],470index_range)471j_secondary_start, j_secondary_step = index_to_start_step_2d(secondary_indices[2],472index_range)473474i_secondary = i_secondary_start475j_secondary = j_secondary_start476for i in eachnode(dg)477# This is the outward normal direction on the secondary element.478# Here, we assume that normal_direction on the secondary element is479# the negative of normal_direction on the primary element.480normal_direction = get_normal_direction(secondary_direction,481contravariant_vectors,482i_secondary, j_secondary,483secondary_element)484485for v in eachvariable(equations_parabolic)486# OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*!487flux_parabolic = SVector(flux_parabolic_x[v, i_secondary, j_secondary,488secondary_element],489flux_parabolic_y[v, i_secondary, j_secondary,490secondary_element])491# store the normal flux with respect to the primary normal direction,492# which is the negative of the secondary normal direction493interfaces.u[2, v, i, interface] = -dot(flux_parabolic,494normal_direction)495end496i_secondary += i_secondary_step497j_secondary += j_secondary_step498end499end500501return nothing502end503504# This version is used for divergence flux computations505function calc_interface_flux!(surface_flux_values, mesh::P4estMesh{2},506equations_parabolic, dg::DG, parabolic_scheme,507cache)508(; neighbor_ids, node_indices) = cache.interfaces509@unpack contravariant_vectors = cache.elements510index_range = eachnode(dg)511index_end = last(index_range)512513@threaded for interface in eachinterface(dg, cache)514# Get element and side index information on the primary element515primary_element = neighbor_ids[1, interface]516primary_indices = node_indices[1, interface]517primary_direction_index = indices2direction(primary_indices)518519# Create the local i,j indexing on the primary element used to pull normal direction information520i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1],521index_range)522j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2],523index_range)524525i_primary = i_primary_start526j_primary = j_primary_start527528# Get element and side index information on the secondary element529secondary_element = neighbor_ids[2, interface]530secondary_indices = node_indices[2, interface]531secondary_direction_index = indices2direction(secondary_indices)532533# Initiate the secondary index to be used in the surface for loop.534# This index on the primary side will always run forward but535# the secondary index might need to run backwards for flipped sides.536if :i_backward in secondary_indices537node_secondary = index_end538node_secondary_step = -1539else540node_secondary = 1541node_secondary_step = 1542end543544for i in eachnode(dg)545normal_direction = get_normal_direction(primary_direction_index,546contravariant_vectors,547i_primary, j_primary,548primary_element)549550# We prolong the parabolic flux dotted with respect the outward normal on the551# primary element.552parabolic_flux_normal_ll, parabolic_flux_normal_rr = get_surface_node_vars(cache.interfaces.u,553equations_parabolic,554dg,555i,556interface)557558flux_ = flux_parabolic(parabolic_flux_normal_ll, parabolic_flux_normal_rr,559normal_direction, Divergence(),560equations_parabolic, parabolic_scheme)561562for v in eachvariable(equations_parabolic)563surface_flux_values[v, i, primary_direction_index, primary_element] = flux_[v]564# Sign flip required for divergence calculation since the divergence interface flux565# involves the normal direction.566surface_flux_values[v, node_secondary, secondary_direction_index, secondary_element] = -flux_[v]567end568569# Increment primary element indices to pull the normal direction570i_primary += i_primary_step571j_primary += j_primary_step572# Increment the surface node index along the secondary element573node_secondary += node_secondary_step574end575end576577return nothing578end579580function prolong2mortars_divergence!(cache, flux_parabolic,581mesh::P4estMesh{2}, equations_parabolic,582mortar_l2::LobattoLegendreMortarL2,583dg::DGSEM)584@unpack neighbor_ids, node_indices = cache.mortars585@unpack contravariant_vectors = cache.elements586index_range = eachnode(dg)587588flux_parabolic_x, flux_parabolic_y = flux_parabolic589590@threaded for mortar in eachmortar(dg, cache)591# Copy solution data from the small elements using "delayed indexing" with592# a start value and a step size to get the correct face and orientation.593small_indices = node_indices[1, mortar]594direction_index = indices2direction(small_indices)595596i_small_start, i_small_step = index_to_start_step_2d(small_indices[1],597index_range)598j_small_start, j_small_step = index_to_start_step_2d(small_indices[2],599index_range)600601for position in 1:2602i_small = i_small_start603j_small = j_small_start604element = neighbor_ids[position, mortar]605for i in eachnode(dg)606normal_direction = get_normal_direction(direction_index,607contravariant_vectors,608i_small, j_small, element)609610for v in eachvariable(equations_parabolic)611flux_parabolic = SVector(flux_parabolic_x[v, i_small, j_small,612element],613flux_parabolic_y[v, i_small, j_small,614element])615616cache.mortars.u[1, v, position, i, mortar] = dot(flux_parabolic,617normal_direction)618end619i_small += i_small_step620j_small += j_small_step621end622end623624# Buffer to copy solution values of the large element in the correct orientation625# before interpolating626u_buffer = cache.u_threaded[Threads.threadid()]627628# Copy solution of large element face to buffer in the629# correct orientation630large_indices = node_indices[2, mortar]631direction_index = indices2direction(large_indices)632633i_large_start, i_large_step = index_to_start_step_2d(large_indices[1],634index_range)635j_large_start, j_large_step = index_to_start_step_2d(large_indices[2],636index_range)637638i_large = i_large_start639j_large = j_large_start640element = neighbor_ids[3, mortar]641for i in eachnode(dg)642normal_direction = get_normal_direction(direction_index,643contravariant_vectors,644i_large, j_large, element)645646for v in eachvariable(equations_parabolic)647flux_parabolic = SVector(flux_parabolic_x[v, i_large, j_large, element],648flux_parabolic_y[v, i_large, j_large, element])649650# We prolong the parabolic flux dotted with respect the outward normal651# on the small element. We scale by -1/2 here because the normal652# direction on the large element is negative 2x that of the small653# element (these normal directions are "scaled" by the surface Jacobian)654u_buffer[v, i] = -0.5f0 * dot(flux_parabolic, normal_direction)655end656i_large += i_large_step657j_large += j_large_step658end659660# Interpolate large element face data from buffer to small face locations661multiply_dimensionwise!(view(cache.mortars.u, 2, :, 1, :, mortar),662mortar_l2.forward_lower, u_buffer)663multiply_dimensionwise!(view(cache.mortars.u, 2, :, 2, :, mortar),664mortar_l2.forward_upper, u_buffer)665end666667return nothing668end669670function calc_mortar_flux_divergence!(surface_flux_values, mesh::P4estMesh{2},671equations_parabolic::AbstractEquationsParabolic,672mortar_l2::LobattoLegendreMortarL2,673dg::DG, parabolic_scheme, cache)674@unpack neighbor_ids, node_indices = cache.mortars675@unpack contravariant_vectors = cache.elements676@unpack fstar_primary_upper_threaded, fstar_primary_lower_threaded = cache677index_range = eachnode(dg)678679@threaded for mortar in eachmortar(dg, cache)680# Choose thread-specific pre-allocated container.681# Using only `fstar_primary` is sufficient682fstar = (fstar_primary_lower_threaded[Threads.threadid()],683fstar_primary_upper_threaded[Threads.threadid()])684685# Get index information on the small elements686small_indices = node_indices[1, mortar]687small_direction = indices2direction(small_indices)688689i_small_start, i_small_step = index_to_start_step_2d(small_indices[1],690index_range)691j_small_start, j_small_step = index_to_start_step_2d(small_indices[2],692index_range)693694for position in 1:2695i_small = i_small_start696j_small = j_small_start697element = neighbor_ids[position, mortar]698for i in eachnode(dg)699normal_direction = get_normal_direction(small_direction,700contravariant_vectors,701i_small, j_small, element)702703for v in eachvariable(equations_parabolic)704parabolic_flux_normal_ll = cache.mortars.u[1, v, position, i,705mortar]706parabolic_flux_normal_rr = cache.mortars.u[2, v, position, i,707mortar]708709flux_ = flux_parabolic(parabolic_flux_normal_ll,710parabolic_flux_normal_rr,711normal_direction, Divergence(),712equations_parabolic, parabolic_scheme)713714# Sign flip (and scaling by 0.5) already handled above in `prolong2mortars_divergence!`715fstar[position][v, i] = flux_716end717i_small += i_small_step718j_small += j_small_step719end720end721722# Buffer to interpolate flux values of the large element to before723# copying in the correct orientation724u_buffer = cache.u_threaded[Threads.threadid()]725726# this reuses the hyperbolic version of `mortar_fluxes_to_elements!`727mortar_fluxes_to_elements!(surface_flux_values,728mesh, equations_parabolic, mortar_l2, dg, cache,729mortar, fstar, fstar, u_buffer)730end731732return nothing733end734735function calc_mortar_flux_gradient!(surface_flux_values,736mesh::P4estMesh{2},737equations_parabolic,738mortar_l2::LobattoLegendreMortarL2,739dg::DG, parabolic_scheme, cache)740@unpack neighbor_ids, node_indices = cache.mortars741@unpack contravariant_vectors = cache.elements742@unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded,743fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache744index_range = eachnode(dg)745746@threaded for mortar in eachmortar(dg, cache)747# Choose thread-specific pre-allocated container748fstar_primary = (fstar_primary_lower_threaded[Threads.threadid()],749fstar_primary_upper_threaded[Threads.threadid()])750751fstar_secondary = (fstar_secondary_lower_threaded[Threads.threadid()],752fstar_secondary_upper_threaded[Threads.threadid()])753754# Get index information on the small elements755small_indices = node_indices[1, mortar]756small_direction = indices2direction(small_indices)757758i_small_start, i_small_step = index_to_start_step_2d(small_indices[1],759index_range)760j_small_start, j_small_step = index_to_start_step_2d(small_indices[2],761index_range)762763for position in 1:2764i_small = i_small_start765j_small = j_small_start766element = neighbor_ids[position, mortar]767for i in eachnode(dg)768normal_direction = get_normal_direction(small_direction,769contravariant_vectors,770i_small, j_small, element)771772calc_mortar_flux_gradient!(fstar_primary, fstar_secondary,773mesh, equations_parabolic,774dg, parabolic_scheme, cache,775mortar, position, normal_direction, i)776777i_small += i_small_step778j_small += j_small_step779end780end781782# Buffer to interpolate flux values of the large element to before783# copying in the correct orientation784u_buffer = cache.u_threaded[Threads.threadid()]785786mortar_fluxes_to_elements_gradient!(surface_flux_values,787mesh, equations_parabolic, mortar_l2,788dg, cache,789mortar, fstar_primary, fstar_secondary,790u_buffer)791end792793return nothing794end795796# We structure `calc_mortar_flux_gradient!` similarly to "calc_mortar_flux!" for797# hyperbolic equations with no nonconservative terms.798# The reasoning is that parabolic fluxes are treated like conservative799# terms (e.g., we compute a parabolic conservative "flux") and thus no800# non-conservative terms are present.801@inline function calc_mortar_flux_gradient!(fstar_primary, fstar_secondary,802mesh::P4estMesh{2}, equations_parabolic,803dg::DG, parabolic_scheme, cache,804mortar_index, position_index,805normal_direction, node_index)806@unpack u = cache.mortars807808u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg,809position_index, node_index, mortar_index)810811flux_ = flux_parabolic(u_ll, u_rr, normal_direction, Gradient(),812equations_parabolic, parabolic_scheme)813814# Copy flux to buffer815set_node_vars!(fstar_primary[position_index], flux_, equations_parabolic, dg,816node_index)817# As in `calc_interface_flux_gradient!`, no sign flip is necessary for the fluxes used in gradient818# because for parabolic terms, the normals are not embedded in `flux_` for gradient computations.819set_node_vars!(fstar_secondary[position_index], flux_, equations_parabolic, dg,820node_index)821822return nothing823end824825# Specialization `flux_parabolic::Tuple` needed to826# avoid amibiguity with the hyperbolic version of `prolong2boundaries!` in dg_2d.jl827# which is for the variables itself, i.e., `u::Array{uEltype, 4}`.828function prolong2boundaries!(cache, flux_parabolic::Tuple,829mesh::P4estMesh{2},830equations_parabolic::AbstractEquationsParabolic,831dg::DG)832(; boundaries) = cache833(; contravariant_vectors) = cache.elements834index_range = eachnode(dg)835836flux_parabolic_x, flux_parabolic_y = flux_parabolic837838@threaded for boundary in eachboundary(dg, cache)839# Copy solution data from the element using "delayed indexing" with840# a start value and a step size to get the correct face and orientation.841element = boundaries.neighbor_ids[boundary]842node_indices = boundaries.node_indices[boundary]843direction = indices2direction(node_indices)844845i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range)846j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range)847848i_node = i_node_start849j_node = j_node_start850for i in eachnode(dg)851# this is the outward normal direction on the primary element852normal_direction = get_normal_direction(direction, contravariant_vectors,853i_node, j_node, element)854855for v in eachvariable(equations_parabolic)856flux_parabolic = SVector(flux_parabolic_x[v, i_node, j_node, element],857flux_parabolic_y[v, i_node, j_node, element])858859boundaries.u[v, i, boundary] = dot(flux_parabolic, normal_direction)860end861i_node += i_node_step862j_node += j_node_step863end864end865return nothing866end867868function calc_volume_integral_gradient!(gradients, u_transformed,869mesh::P4estMesh{2}, # for dispatch only870equations_parabolic::AbstractEquationsParabolic,871dg::DG, cache)872@unpack derivative_hat = dg.basis873@unpack contravariant_vectors = cache.elements874gradients_x, gradients_y = gradients875876@threaded for element in eachelement(dg, cache)877# Calculate volume terms in one element,878# corresponds to `kernel` functions for the hyperbolic part of the flux879for j in eachnode(dg), i in eachnode(dg)880u_node = get_node_vars(u_transformed, equations_parabolic, dg,881i, j, element)882883for ii in eachnode(dg)884multiply_add_to_node_vars!(gradients_x, derivative_hat[ii, i],885u_node, equations_parabolic, dg,886ii, j, element)887end888889for jj in eachnode(dg)890multiply_add_to_node_vars!(gradients_y, derivative_hat[jj, j],891u_node, equations_parabolic, dg,892i, jj, element)893end894end895896# now that the reference coordinate gradients are computed, transform them node-by-node to physical gradients897# using the contravariant vectors898for j in eachnode(dg), i in eachnode(dg)899Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors,900i, j, element)901Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors,902i, j, element)903904gradients_reference_1 = get_node_vars(gradients_x, equations_parabolic, dg,905i, j, element)906gradients_reference_2 = get_node_vars(gradients_y, equations_parabolic, dg,907i, j, element)908909# note that the contravariant vectors are transposed compared with computations of flux910# divergences in `calc_volume_integral!`. See911# https://github.com/trixi-framework/Trixi.jl/pull/1490#discussion_r1213345190912# for a more detailed discussion.913gradient_x_node = Ja11 * gradients_reference_1 +914Ja21 * gradients_reference_2915gradient_y_node = Ja12 * gradients_reference_1 +916Ja22 * gradients_reference_2917918set_node_vars!(gradients_x, gradient_x_node, equations_parabolic, dg,919i, j, element)920set_node_vars!(gradients_y, gradient_y_node, equations_parabolic, dg,921i, j, element)922end923end924925return nothing926end927928function calc_boundary_flux_gradient!(cache, t,929boundary_condition::Union{BoundaryConditionPeriodic,930BoundaryConditionDoNothing},931mesh::P4estMesh,932equations_parabolic, surface_integral, dg::DG)933@assert isempty(eachboundary(dg, cache))934end935936# Function barrier for type stability937function calc_boundary_flux_gradient!(cache, t, boundary_conditions, mesh::P4estMesh,938equations_parabolic, surface_integral, dg::DG)939(; boundary_condition_types, boundary_indices) = boundary_conditions940941calc_boundary_flux_by_type!(cache, t, boundary_condition_types, boundary_indices,942Gradient(), mesh, equations_parabolic, surface_integral,943dg)944return nothing945end946947function calc_boundary_flux_divergence!(cache, t, boundary_conditions, mesh::P4estMesh,948equations_parabolic, surface_integral, dg::DG)949(; boundary_condition_types, boundary_indices) = boundary_conditions950951calc_boundary_flux_by_type!(cache, t, boundary_condition_types, boundary_indices,952Divergence(), mesh, equations_parabolic,953surface_integral, dg)954return nothing955end956957# Iterate over tuples of boundary condition types and associated indices958# in a type-stable way using "lispy tuple programming".959function calc_boundary_flux_by_type!(cache, t, BCs::NTuple{N, Any},960BC_indices::NTuple{N, Vector{Int}},961operator_type,962mesh::P4estMesh,963equations_parabolic, surface_integral,964dg::DG) where {N}965# Extract the boundary condition type and index vector966boundary_condition = first(BCs)967boundary_condition_indices = first(BC_indices)968# Extract the remaining types and indices to be processed later969remaining_boundary_conditions = Base.tail(BCs)970remaining_boundary_condition_indices = Base.tail(BC_indices)971972# process the first boundary condition type973calc_boundary_flux!(cache, t, boundary_condition, boundary_condition_indices,974operator_type, mesh, equations_parabolic, surface_integral, dg)975976# recursively call this method with the unprocessed boundary types977calc_boundary_flux_by_type!(cache, t, remaining_boundary_conditions,978remaining_boundary_condition_indices,979operator_type,980mesh, equations_parabolic, surface_integral, dg)981982return nothing983end984985# terminate the type-stable iteration over tuples986function calc_boundary_flux_by_type!(cache, t, BCs::Tuple{}, BC_indices::Tuple{},987operator_type, mesh::P4estMesh,988equations_parabolic,989surface_integral, dg::DG)990return nothing991end992993function calc_boundary_flux!(cache, t,994boundary_condition_parabolic, # works with Dict types995boundary_condition_indices,996operator_type, mesh::P4estMesh{2},997equations_parabolic::AbstractEquationsParabolic,998surface_integral, dg::DG)999(; boundaries) = cache1000(; node_coordinates, surface_flux_values) = cache.elements1001(; contravariant_vectors) = cache.elements1002index_range = eachnode(dg)10031004@threaded for local_index in eachindex(boundary_condition_indices)1005# Use the local index to get the global boundary index from the pre-sorted list1006boundary_index = boundary_condition_indices[local_index]10071008# Get information on the adjacent element, compute the surface fluxes,1009# and store them1010element = boundaries.neighbor_ids[boundary_index]1011node_indices = boundaries.node_indices[boundary_index]1012direction_index = indices2direction(node_indices)10131014i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range)1015j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range)10161017i_node = i_node_start1018j_node = j_node_start1019for node_index in eachnode(dg)1020# Extract solution data from boundary container1021u_inner = get_node_vars(boundaries.u, equations_parabolic, dg, node_index,1022boundary_index)10231024# Outward-pointing normal direction (not normalized)1025normal_direction = get_normal_direction(direction_index,1026contravariant_vectors,1027i_node, j_node, element)10281029# TODO: revisit if we want more general boundary treatments.1030# This assumes the gradient numerical flux at the boundary is the gradient variable,1031# which is consistent with BR1, LDG.1032flux_inner = u_inner10331034# Coordinates at boundary node1035x = get_node_coords(node_coordinates, equations_parabolic, dg,1036i_node, j_node, element)10371038flux_ = boundary_condition_parabolic(flux_inner, u_inner, normal_direction,1039x, t, operator_type,1040equations_parabolic)10411042# Copy flux to element storage in the correct orientation1043for v in eachvariable(equations_parabolic)1044surface_flux_values[v, node_index, direction_index, element] = flux_[v]1045end10461047i_node += i_node_step1048j_node += j_node_step1049end1050end10511052return nothing1053end10541055function calc_surface_integral_gradient!(gradients,1056mesh::P4estMesh{2}, # for dispatch only1057equations_parabolic::AbstractEquationsParabolic,1058dg::DGSEM, cache)1059@unpack inverse_weights = dg.basis1060@unpack surface_flux_values = cache.elements1061@unpack contravariant_vectors = cache.elements10621063gradients_x, gradients_y = gradients10641065# We also use explicit assignments instead of `+=` to let `@muladd` turn these1066# into FMAs (see comment at the top of the file).1067factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±11068@threaded for element in eachelement(dg, cache)1069for l in eachnode(dg)1070for v in eachvariable(equations_parabolic)10711072# Compute x-component of gradients10731074# surface at -x1075normal_direction_x, _ = get_normal_direction(1,1076contravariant_vectors,10771, l,1078element)1079gradients_x[v, 1, l, element] = (gradients_x[v,10801, l,1081element] +1082surface_flux_values[v,1083l, 1,1084element] *1085factor *1086normal_direction_x)10871088# surface at +x1089normal_direction_x, _ = get_normal_direction(2,1090contravariant_vectors,1091nnodes(dg), l,1092element)1093gradients_x[v, nnodes(dg), l, element] = (gradients_x[v,1094nnodes(dg), l,1095element] +1096surface_flux_values[v,1097l, 2,1098element] *1099factor *1100normal_direction_x)11011102# surface at -y1103normal_direction_x, _ = get_normal_direction(3,1104contravariant_vectors,1105l, 1,1106element)1107gradients_x[v, l, 1, element] = (gradients_x[v,1108l, 1,1109element] +1110surface_flux_values[v,1111l, 3,1112element] *1113factor *1114normal_direction_x)11151116# surface at +y1117normal_direction_x, _ = get_normal_direction(4,1118contravariant_vectors,1119l, nnodes(dg),1120element)1121gradients_x[v, l, nnodes(dg), element] = (gradients_x[v,1122l, nnodes(dg),1123element] +1124surface_flux_values[v,1125l, 4,1126element] *1127factor *1128normal_direction_x)11291130# Compute y-component of gradients11311132# surface at -x1133_, normal_direction_y = get_normal_direction(1,1134contravariant_vectors,11351, l,1136element)1137gradients_y[v, 1, l, element] = (gradients_y[v,11381, l,1139element] +1140surface_flux_values[v,1141l, 1,1142element] *1143factor *1144normal_direction_y)11451146# surface at +x1147_, normal_direction_y = get_normal_direction(2,1148contravariant_vectors,1149nnodes(dg), l,1150element)1151gradients_y[v, nnodes(dg), l, element] = (gradients_y[v,1152nnodes(dg), l,1153element] +1154surface_flux_values[v,1155l, 2,1156element] *1157factor *1158normal_direction_y)11591160# surface at -y1161_, normal_direction_y = get_normal_direction(3,1162contravariant_vectors,1163l, 1,1164element)1165gradients_y[v, l, 1, element] = (gradients_y[v,1166l, 1,1167element] +1168surface_flux_values[v,1169l, 3,1170element] *1171factor *1172normal_direction_y)11731174# surface at +y1175_, normal_direction_y = get_normal_direction(4,1176contravariant_vectors,1177l, nnodes(dg),1178element)1179gradients_y[v, l, nnodes(dg), element] = (gradients_y[v,1180l, nnodes(dg),1181element] +1182surface_flux_values[v,1183l, 4,1184element] *1185factor *1186normal_direction_y)1187end1188end1189end11901191return nothing1192end11931194# Needed to *not* flip the sign of the inverse Jacobian.1195# This is because the parabolic fluxes are assumed to be of the form1196# `du/dt + df/dx = dg/dx + source(x,t)`,1197# where f(u) is the inviscid flux and g(u) is the parabolic flux.1198function apply_jacobian_parabolic!(du::AbstractArray, mesh::P4estMesh{2},1199equations_parabolic::AbstractEquationsParabolic,1200dg::DG, cache)1201@unpack inverse_jacobian = cache.elements12021203@threaded for element in eachelement(dg, cache)1204for j in eachnode(dg), i in eachnode(dg)1205factor = inverse_jacobian[i, j, element]12061207for v in eachvariable(equations_parabolic)1208du[v, i, j, element] *= factor1209end1210end1211end12121213return nothing1214end1215end # @muladd121612171218