Path: blob/main/src/solvers/dgsem_tree/dg_3d_parabolic.jl
5591 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67# This method is called when a `SemidiscretizationHyperbolicParabolic` is constructed.8# It constructs the basic `cache` used throughout the simulation to compute9# the RHS etc.10function create_cache_parabolic(mesh::Union{TreeMesh{3}, P4estMesh{3}},11equations_hyperbolic::AbstractEquations,12dg::DG, n_elements, uEltype)13parabolic_container = init_parabolic_container_3d(nvariables(equations_hyperbolic),14nnodes(dg), n_elements,15uEltype)1617cache_parabolic = (; parabolic_container)1819return cache_parabolic20end2122# Transform solution variables prior to taking the gradient23# (e.g., conservative to primitive variables). Defaults to doing nothing.24# TODO: can we avoid copying data?25function transform_variables!(u_transformed, u, mesh::Union{TreeMesh{3}, P4estMesh{3}},26equations_parabolic::AbstractEquationsParabolic,27dg::DG, cache)28transformation = gradient_variable_transformation(equations_parabolic)2930@threaded for element in eachelement(dg, cache)31# Calculate volume terms in one element32for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)33u_node = get_node_vars(u, equations_parabolic, dg, i, j, k, element)34u_transformed_node = transformation(u_node, equations_parabolic)35set_node_vars!(u_transformed, u_transformed_node, equations_parabolic, dg,36i, j, k, element)37end38end3940return nothing41end4243function reset_gradients!(gradients::NTuple{3}, dg::DG, cache)44gradients_x, gradients_y, gradients_z = gradients4546set_zero!(gradients_x, dg, cache)47set_zero!(gradients_y, dg, cache)48set_zero!(gradients_z, dg, cache)4950return nothing51end5253# This is the version used when calculating the divergence of the parabolic fluxes.54# Identical to weak-form volume integral/kernel for the purely hyperbolic case,55# except that the fluxes are here already precomputed in `calc_parabolic_fluxes!`56function calc_volume_integral!(du, flux_parabolic, mesh::TreeMesh{3},57equations_parabolic::AbstractEquationsParabolic,58dg::DGSEM, cache)59@unpack derivative_hat = dg.basis60flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic6162@threaded for element in eachelement(dg, cache)63# Calculate volume terms in one element64for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)65flux_1_node = get_node_vars(flux_parabolic_x, equations_parabolic, dg,66i, j, k, element)67flux_2_node = get_node_vars(flux_parabolic_y, equations_parabolic, dg,68i, j, k, element)69flux_3_node = get_node_vars(flux_parabolic_z, equations_parabolic, dg,70i, j, k, element)7172for ii in eachnode(dg)73multiply_add_to_node_vars!(du, derivative_hat[ii, i], flux_1_node,74equations_parabolic, dg, ii, j, k, element)75end7677for jj in eachnode(dg)78multiply_add_to_node_vars!(du, derivative_hat[jj, j], flux_2_node,79equations_parabolic, dg, i, jj, k, element)80end8182for kk in eachnode(dg)83multiply_add_to_node_vars!(du, derivative_hat[kk, k], flux_3_node,84equations_parabolic, dg, i, j, kk, element)85end86end87end8889return nothing90end9192# This is the version used when calculating the divergence of the parabolic fluxes.93# Specialization `flux_parabolic::Tuple` needed to94# avoid amibiguity with the hyperbolic version of `prolong2interfaces!` in dg_3d.jl95# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.96function prolong2interfaces!(cache, flux_parabolic::Tuple,97mesh::TreeMesh{3},98equations_parabolic::AbstractEquationsParabolic,99dg::DG)100@unpack interfaces = cache101@unpack orientations, neighbor_ids = interfaces102103# OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*!104interfaces_u = interfaces.u105106flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic107108@threaded for interface in eachinterface(dg, cache)109left_element = neighbor_ids[1, interface]110right_element = neighbor_ids[2, interface]111112if orientations[interface] == 1113# interface in x-direction114for k in eachnode(dg), j in eachnode(dg),115v in eachvariable(equations_parabolic)116117interfaces_u[1, v, j, k, interface] = flux_parabolic_x[v,118nnodes(dg), j, k,119left_element]120interfaces_u[2, v, j, k, interface] = flux_parabolic_x[v,1211, j, k,122right_element]123end124elseif orientations[interface] == 2125# interface in y-direction126for k in eachnode(dg), i in eachnode(dg),127v in eachvariable(equations_parabolic)128129interfaces_u[1, v, i, k, interface] = flux_parabolic_y[v,130i, nnodes(dg), k,131left_element]132interfaces_u[2, v, i, k, interface] = flux_parabolic_y[v,133i, 1, k,134right_element]135end136else # if orientations[interface] == 3137# interface in z-direction138for j in eachnode(dg), i in eachnode(dg),139v in eachvariable(equations_parabolic)140141interfaces_u[1, v, i, j, interface] = flux_parabolic_z[v,142i, j, nnodes(dg),143left_element]144interfaces_u[2, v, i, j, interface] = flux_parabolic_z[v,145i, j, 1,146right_element]147end148end149end150151return nothing152end153154# This is the version used when calculating the divergence of the parabolic fluxes155function calc_interface_flux!(surface_flux_values, mesh::TreeMesh{3},156equations_parabolic, dg::DG, parabolic_scheme,157cache)158@unpack neighbor_ids, orientations = cache.interfaces159160@threaded for interface in eachinterface(dg, cache)161# Get neighboring elements162left_id = neighbor_ids[1, interface]163right_id = neighbor_ids[2, interface]164165# Determine interface direction with respect to elements:166# orientation = 1: left -> 2, right -> 1167# orientation = 2: left -> 4, right -> 3168# orientation = 3: left -> 6, right -> 5169left_direction = 2 * orientations[interface]170right_direction = 2 * orientations[interface] - 1171172for j in eachnode(dg), i in eachnode(dg)173# Get precomputed fluxes at interfaces174flux_ll, flux_rr = get_surface_node_vars(cache.interfaces.u,175equations_parabolic, dg,176i, j, interface)177178flux = flux_parabolic(flux_ll, flux_rr, Divergence(),179equations_parabolic, parabolic_scheme)180181# Copy flux to left and right element storage182for v in eachvariable(equations_parabolic)183surface_flux_values[v, i, j, left_direction, left_id] = flux[v]184surface_flux_values[v, i, j, right_direction, right_id] = flux[v]185end186end187end188189return nothing190end191192# This is the version used when calculating the divergence of the parabolic fluxes.193# Specialization `flux_parabolic::Tuple` needed to194# avoid amibiguity with the hyperbolic version of `prolong2boundaries!` in dg_3d.jl195# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.196function prolong2boundaries!(cache, flux_parabolic::Tuple,197mesh::TreeMesh{3},198equations_parabolic::AbstractEquationsParabolic,199dg::DG)200@unpack boundaries = cache201@unpack orientations, neighbor_sides, neighbor_ids = boundaries202203# OBS! `boundaries_u` stores the "interpolated" *fluxes* and *not the solution*!204boundaries_u = boundaries.u205flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic206207@threaded for boundary in eachboundary(dg, cache)208element = neighbor_ids[boundary]209210if orientations[boundary] == 1211# boundary in x-direction212if neighbor_sides[boundary] == 1213# element in -x direction of boundary214for k in eachnode(dg), j in eachnode(dg),215v in eachvariable(equations_parabolic)216217boundaries_u[1, v, j, k, boundary] = flux_parabolic_x[v,218nnodes(dg),219j,220k,221element]222end223else # Element in +x direction of boundary224for k in eachnode(dg), j in eachnode(dg),225v in eachvariable(equations_parabolic)226227boundaries_u[2, v, j, k, boundary] = flux_parabolic_x[v,2281,229j,230k,231element]232end233end234elseif orientations[boundary] == 2235# boundary in y-direction236if neighbor_sides[boundary] == 1237# element in -y direction of boundary238for k in eachnode(dg), i in eachnode(dg),239v in eachvariable(equations_parabolic)240241boundaries_u[1, v, i, k, boundary] = flux_parabolic_y[v,242i,243nnodes(dg),244k,245element]246end247else248# element in +y direction of boundary249for k in eachnode(dg), i in eachnode(dg),250v in eachvariable(equations_parabolic)251252boundaries_u[2, v, i, k, boundary] = flux_parabolic_y[v,253i,2541,255k,256element]257end258end259else # if orientations[boundary] == 3260# boundary in z-direction261if neighbor_sides[boundary] == 1262# element in -z direction of boundary263for j in eachnode(dg), i in eachnode(dg),264v in eachvariable(equations_parabolic)265266boundaries_u[1, v, i, j, boundary] = flux_parabolic_z[v,267i,268j,269nnodes(dg),270element]271end272else273# element in +z direction of boundary274for j in eachnode(dg), i in eachnode(dg),275v in eachvariable(equations_parabolic)276277boundaries_u[2, v, i, j, boundary] = flux_parabolic_z[v,278i,279j,2801,281element]282end283end284end285end286287return nothing288end289290function calc_parabolic_fluxes!(flux_parabolic,291gradients, u_transformed,292mesh::Union{TreeMesh{3}, P4estMesh{3}},293equations_parabolic::AbstractEquationsParabolic,294dg::DG, cache)295gradients_x, gradients_y, gradients_z = gradients296flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic # output arrays297298@threaded for element in eachelement(dg, cache)299for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)300# Get solution and gradients301u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, k,302element)303gradients_1_node = get_node_vars(gradients_x, equations_parabolic, dg,304i, j, k, element)305gradients_2_node = get_node_vars(gradients_y, equations_parabolic, dg,306i, j, k, element)307gradients_3_node = get_node_vars(gradients_z, equations_parabolic, dg,308i, j, k, element)309310# Calculate parabolic flux and store each component for later use311flux_parabolic_node_x = flux(u_node,312(gradients_1_node, gradients_2_node,313gradients_3_node), 1, equations_parabolic)314flux_parabolic_node_y = flux(u_node,315(gradients_1_node, gradients_2_node,316gradients_3_node), 2, equations_parabolic)317flux_parabolic_node_z = flux(u_node,318(gradients_1_node, gradients_2_node,319gradients_3_node), 3, equations_parabolic)320set_node_vars!(flux_parabolic_x, flux_parabolic_node_x,321equations_parabolic, dg,322i, j, k, element)323set_node_vars!(flux_parabolic_y, flux_parabolic_node_y,324equations_parabolic, dg,325i, j, k, element)326set_node_vars!(flux_parabolic_z, flux_parabolic_node_z,327equations_parabolic, dg,328i, j, k, element)329end330end331332return nothing333end334335# TODO: parabolic; decide if we should keep this.336function get_unsigned_normal_vector_3d(direction)337if direction > 6 || direction < 1338error("Direction = $direction; in 3D, direction should be 1, 2, 3, 4, 5, or 6.")339end340if direction == 1 || direction == 2341return SVector(1.0, 0.0, 0.0)342elseif direction == 3 || direction == 4343return SVector(0.0, 1.0, 0.0)344else345return SVector(0.0, 0.0, 1.0)346end347end348349function calc_boundary_flux_gradient!(cache, t,350boundary_conditions_parabolic::BoundaryConditionPeriodic,351mesh::Union{TreeMesh{3}, P4estMesh{3}},352equations_parabolic::AbstractEquationsParabolic,353surface_integral, dg::DG)354return nothing355end356357function calc_boundary_flux_divergence!(cache, t,358boundary_conditions_parabolic::BoundaryConditionPeriodic,359mesh::Union{TreeMesh{3}, P4estMesh{3}},360equations_parabolic::AbstractEquationsParabolic,361surface_integral, dg::DG)362return nothing363end364365function calc_boundary_flux_gradient!(cache, t,366boundary_conditions_parabolic::NamedTuple,367mesh::TreeMesh{3}, # for dispatch only368equations_parabolic::AbstractEquationsParabolic,369surface_integral, dg::DG)370@unpack surface_flux_values = cache.elements371@unpack n_boundaries_per_direction = cache.boundaries372373# Calculate indices374lasts = accumulate(+, n_boundaries_per_direction)375firsts = lasts - n_boundaries_per_direction .+ 1376377# Calc boundary fluxes in each direction378calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,379boundary_conditions_parabolic[1],380equations_parabolic, surface_integral,381dg, cache,3821, firsts[1], lasts[1])383calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,384boundary_conditions_parabolic[2],385equations_parabolic, surface_integral,386dg, cache,3872, firsts[2], lasts[2])388calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,389boundary_conditions_parabolic[3],390equations_parabolic, surface_integral,391dg, cache,3923, firsts[3], lasts[3])393calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,394boundary_conditions_parabolic[4],395equations_parabolic, surface_integral,396dg, cache,3974, firsts[4], lasts[4])398calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,399boundary_conditions_parabolic[5],400equations_parabolic, surface_integral,401dg, cache,4025, firsts[5], lasts[5])403calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,404boundary_conditions_parabolic[6],405equations_parabolic, surface_integral,406dg, cache,4076, firsts[6], lasts[6])408409return nothing410end411412function calc_boundary_flux_by_direction_gradient!(surface_flux_values::AbstractArray{<:Any,4135},414t, boundary_condition,415equations_parabolic::AbstractEquationsParabolic,416surface_integral, dg::DG, cache,417direction, first_boundary,418last_boundary)419@unpack surface_flux = surface_integral420@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries421422@threaded for boundary in first_boundary:last_boundary423# Get neighboring element424neighbor = neighbor_ids[boundary]425426for j in eachnode(dg), i in eachnode(dg)427# Get boundary flux428u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg,429i, j, boundary)430if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right431u_inner = u_ll432else # Element is on the right, boundary on the left433u_inner = u_rr434end435436# TODO: revisit if we want more general boundary treatments.437# This assumes the gradient numerical flux at the boundary is the gradient variable,438# which is consistent with BR1, LDG.439flux_inner = u_inner440441x = get_node_coords(node_coordinates, equations_parabolic, dg,442i, j, boundary)443flux = boundary_condition(flux_inner, u_inner,444get_unsigned_normal_vector_3d(direction),445x, t, Gradient(), equations_parabolic)446447# Copy flux to left and right element storage448for v in eachvariable(equations_parabolic)449surface_flux_values[v, i, j, direction, neighbor] = flux[v]450end451end452end453454return nothing455end456457function calc_boundary_flux_divergence!(cache, t,458boundary_conditions_parabolic::NamedTuple,459mesh::TreeMesh{3},460equations_parabolic::AbstractEquationsParabolic,461surface_integral, dg::DG)462@unpack surface_flux_values = cache.elements463@unpack n_boundaries_per_direction = cache.boundaries464465# Calculate indices466lasts = accumulate(+, n_boundaries_per_direction)467firsts = lasts - n_boundaries_per_direction .+ 1468469# Calc boundary fluxes in each direction470calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,471boundary_conditions_parabolic[1],472equations_parabolic, surface_integral,473dg, cache,4741, firsts[1], lasts[1])475calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,476boundary_conditions_parabolic[2],477equations_parabolic, surface_integral,478dg, cache,4792, firsts[2], lasts[2])480calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,481boundary_conditions_parabolic[3],482equations_parabolic, surface_integral,483dg, cache,4843, firsts[3], lasts[3])485calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,486boundary_conditions_parabolic[4],487equations_parabolic, surface_integral,488dg, cache,4894, firsts[4], lasts[4])490calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,491boundary_conditions_parabolic[5],492equations_parabolic, surface_integral,493dg, cache,4945, firsts[5], lasts[5])495calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,496boundary_conditions_parabolic[6],497equations_parabolic, surface_integral,498dg, cache,4996, firsts[6], lasts[6])500501return nothing502end503504function calc_boundary_flux_by_direction_divergence!(surface_flux_values::AbstractArray{<:Any,5055},506t,507boundary_condition,508equations_parabolic::AbstractEquationsParabolic,509surface_integral, dg::DG, cache,510direction, first_boundary,511last_boundary)512@unpack surface_flux = surface_integral513514# Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction")515# of the parabolic flux, as computed in `prolong2boundaries!`516@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries517518@threaded for boundary in first_boundary:last_boundary519# Get neighboring element520neighbor = neighbor_ids[boundary]521522for j in eachnode(dg), i in eachnode(dg)523# Get parabolic boundary fluxes524flux_ll, flux_rr = get_surface_node_vars(u, equations_parabolic, dg,525i, j, boundary)526if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right527flux_inner = flux_ll528else # Element is on the right, boundary on the left529flux_inner = flux_rr530end531532x = get_node_coords(node_coordinates, equations_parabolic, dg,533i, j, boundary)534535# TODO: add a field in `cache.boundaries` for gradient information. UPDATE THIS COMMENT536# Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information.537# This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion3D and538# NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion3D as of 2022-6-27.539# It will not work with implementations which utilize `u_inner` to impose boundary conditions, such as540# the `Slip` boundary condition, which can be imposed to realize reflective or symmetric boundaries.541flux = boundary_condition(flux_inner, nothing,542get_unsigned_normal_vector_3d(direction),543x, t, Divergence(), equations_parabolic)544545# Copy flux to left and right element storage546for v in eachvariable(equations_parabolic)547surface_flux_values[v, i, j, direction, neighbor] = flux[v]548end549end550end551552return nothing553end554555# Specialization `flux_parabolic::Tuple` needed to556# avoid amibiguity with the hyperbolic version of `prolong2mortars!` in dg_3d.jl557# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.558function prolong2mortars!(cache, flux_parabolic::Tuple,559mesh::TreeMesh{3},560equations_parabolic::AbstractEquationsParabolic,561mortar_l2::LobattoLegendreMortarL2, dg::DGSEM)562# temporary buffer for projections563@unpack fstar_tmp1_threaded = cache564565flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic566@threaded for mortar in eachmortar(dg, cache)567fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()]568569lower_left_element = cache.mortars.neighbor_ids[1, mortar]570lower_right_element = cache.mortars.neighbor_ids[2, mortar]571upper_left_element = cache.mortars.neighbor_ids[3, mortar]572upper_right_element = cache.mortars.neighbor_ids[4, mortar]573large_element = cache.mortars.neighbor_ids[5, mortar]574575# Copy solution small to small576if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side577if cache.mortars.orientations[mortar] == 1578# L2 mortars in x-direction579for k in eachnode(dg), j in eachnode(dg)580for v in eachvariable(equations_parabolic)581cache.mortars.u_upper_left[2, v, j, k, mortar] = flux_parabolic_x[v,5821,583j,584k,585upper_left_element]586cache.mortars.u_upper_right[2, v, j, k, mortar] = flux_parabolic_x[v,5871,588j,589k,590upper_right_element]591cache.mortars.u_lower_left[2, v, j, k, mortar] = flux_parabolic_x[v,5921,593j,594k,595lower_left_element]596cache.mortars.u_lower_right[2, v, j, k, mortar] = flux_parabolic_x[v,5971,598j,599k,600lower_right_element]601end602end603elseif cache.mortars.orientations[mortar] == 2604# L2 mortars in y-direction605for k in eachnode(dg), i in eachnode(dg)606for v in eachvariable(equations_parabolic)607cache.mortars.u_upper_left[2, v, i, k, mortar] = flux_parabolic_y[v,608i,6091,610k,611upper_left_element]612cache.mortars.u_upper_right[2, v, i, k, mortar] = flux_parabolic_y[v,613i,6141,615k,616upper_right_element]617cache.mortars.u_lower_left[2, v, i, k, mortar] = flux_parabolic_y[v,618i,6191,620k,621lower_left_element]622cache.mortars.u_lower_right[2, v, i, k, mortar] = flux_parabolic_y[v,623i,6241,625k,626lower_right_element]627end628end629else # orientations[mortar] == 3630# L2 mortars in z-direction631for j in eachnode(dg), i in eachnode(dg)632for v in eachvariable(equations_parabolic)633cache.mortars.u_upper_left[2, v, i, j, mortar] = flux_parabolic_z[v,634i,635j,6361,637upper_left_element]638cache.mortars.u_upper_right[2, v, i, j, mortar] = flux_parabolic_z[v,639i,640j,6411,642upper_right_element]643cache.mortars.u_lower_left[2, v, i, j, mortar] = flux_parabolic_z[v,644i,645j,6461,647lower_left_element]648cache.mortars.u_lower_right[2, v, i, j, mortar] = flux_parabolic_z[v,649i,650j,6511,652lower_right_element]653end654end655end656else # large_sides[mortar] == 2 -> small elements on left side657if cache.mortars.orientations[mortar] == 1658# L2 mortars in x-direction659for k in eachnode(dg), j in eachnode(dg)660for v in eachvariable(equations_parabolic)661cache.mortars.u_upper_left[1, v, j, k, mortar] = flux_parabolic_x[v,662nnodes(dg),663j,664k,665upper_left_element]666cache.mortars.u_upper_right[1, v, j, k, mortar] = flux_parabolic_x[v,667nnodes(dg),668j,669k,670upper_right_element]671cache.mortars.u_lower_left[1, v, j, k, mortar] = flux_parabolic_x[v,672nnodes(dg),673j,674k,675lower_left_element]676cache.mortars.u_lower_right[1, v, j, k, mortar] = flux_parabolic_x[v,677nnodes(dg),678j,679k,680lower_right_element]681end682end683elseif cache.mortars.orientations[mortar] == 2684# L2 mortars in y-direction685for k in eachnode(dg), i in eachnode(dg)686for v in eachvariable(equations_parabolic)687cache.mortars.u_upper_left[1, v, i, k, mortar] = flux_parabolic_y[v,688i,689nnodes(dg),690k,691upper_left_element]692cache.mortars.u_upper_right[1, v, i, k, mortar] = flux_parabolic_y[v,693i,694nnodes(dg),695k,696upper_right_element]697cache.mortars.u_lower_left[1, v, i, k, mortar] = flux_parabolic_y[v,698i,699nnodes(dg),700k,701lower_left_element]702cache.mortars.u_lower_right[1, v, i, k, mortar] = flux_parabolic_y[v,703i,704nnodes(dg),705k,706lower_right_element]707end708end709else # if cache.mortars.orientations[mortar] == 3710# L2 mortars in z-direction711for j in eachnode(dg), i in eachnode(dg)712for v in eachvariable(equations_parabolic)713cache.mortars.u_upper_left[1, v, i, j, mortar] = flux_parabolic_z[v,714i,715j,716nnodes(dg),717upper_left_element]718cache.mortars.u_upper_right[1, v, i, j, mortar] = flux_parabolic_z[v,719i,720j,721nnodes(dg),722upper_right_element]723cache.mortars.u_lower_left[1, v, i, j, mortar] = flux_parabolic_z[v,724i,725j,726nnodes(dg),727lower_left_element]728cache.mortars.u_lower_right[1, v, i, j, mortar] = flux_parabolic_z[v,729i,730j,731nnodes(dg),732lower_right_element]733end734end735end736end737738# Interpolate large element face data to small interface locations739if cache.mortars.large_sides[mortar] == 1 # -> large element on left side740leftright = 1741if cache.mortars.orientations[mortar] == 1742# L2 mortars in x-direction743u_large = view(flux_parabolic_x, :, nnodes(dg), :, :, large_element)744element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,745mortar, u_large, fstar_tmp1)746elseif cache.mortars.orientations[mortar] == 2747# L2 mortars in y-direction748u_large = view(flux_parabolic_y, :, :, nnodes(dg), :, large_element)749element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,750mortar, u_large, fstar_tmp1)751else # cache.mortars.orientations[mortar] == 3752# L2 mortars in z-direction753u_large = view(flux_parabolic_z, :, :, :, nnodes(dg), large_element)754element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,755mortar, u_large, fstar_tmp1)756end757else # large_sides[mortar] == 2 -> large element on right side758leftright = 2759if cache.mortars.orientations[mortar] == 1760# L2 mortars in x-direction761u_large = view(flux_parabolic_x, :, 1, :, :, large_element)762element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,763mortar, u_large, fstar_tmp1)764elseif cache.mortars.orientations[mortar] == 2765# L2 mortars in y-direction766u_large = view(flux_parabolic_y, :, :, 1, :, large_element)767element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,768mortar, u_large, fstar_tmp1)769else # cache.mortars.orientations[mortar] == 3770# L2 mortars in z-direction771u_large = view(flux_parabolic_z, :, :, :, 1, large_element)772element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,773mortar, u_large, fstar_tmp1)774end775end776end777778return nothing779end780781# NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms.782# Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for783# hyperbolic terms with conserved terms only, i.e., no nonconservative terms.784function calc_mortar_flux!(surface_flux_values, mesh::TreeMesh{3},785equations_parabolic::AbstractEquationsParabolic,786mortar_l2::LobattoLegendreMortarL2, surface_integral,787dg::DG, parabolic_scheme, gradient_or_divergence, cache)788@unpack surface_flux = surface_integral789@unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations = cache.mortars790@unpack (fstar_primary_upper_left_threaded, fstar_primary_upper_right_threaded,791fstar_primary_lower_left_threaded, fstar_primary_lower_right_threaded,792fstar_tmp1_threaded) = cache793794@threaded for mortar in eachmortar(dg, cache)795# Choose thread-specific pre-allocated container796fstar_upper_left = fstar_primary_upper_left_threaded[Threads.threadid()]797fstar_upper_right = fstar_primary_upper_right_threaded[Threads.threadid()]798fstar_lower_left = fstar_primary_lower_left_threaded[Threads.threadid()]799fstar_lower_right = fstar_primary_lower_right_threaded[Threads.threadid()]800fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()]801802# Calculate fluxes803orientation = orientations[mortar]804calc_fstar!(fstar_upper_left, mesh, equations_parabolic,805surface_flux, dg,806parabolic_scheme, gradient_or_divergence,807u_upper_left, mortar, orientation)808calc_fstar!(fstar_upper_right, mesh, equations_parabolic,809surface_flux, dg,810parabolic_scheme, gradient_or_divergence,811u_upper_right, mortar, orientation)812calc_fstar!(fstar_lower_left, mesh, equations_parabolic,813surface_flux, dg,814parabolic_scheme, gradient_or_divergence,815u_lower_left, mortar, orientation)816calc_fstar!(fstar_lower_right, mesh, equations_parabolic,817surface_flux, dg,818parabolic_scheme, gradient_or_divergence,819u_lower_right, mortar, orientation)820821mortar_fluxes_to_elements!(surface_flux_values,822mesh, equations_parabolic, mortar_l2, dg, cache,823mortar, fstar_upper_left, fstar_upper_right,824fstar_lower_left, fstar_lower_right,825fstar_tmp1)826end827828return nothing829end830831@inline function calc_fstar!(destination::AbstractArray{<:Any, 3},832mesh, equations_parabolic::AbstractEquationsParabolic,833surface_flux, dg::DGSEM,834parabolic_scheme, gradient_or_divergence,835u_interfaces, interface, orientation)836for j in eachnode(dg), i in eachnode(dg)837# Call pointwise two-point numerical flux function838u_ll, u_rr = get_surface_node_vars(u_interfaces, equations_parabolic, dg,839i, j, interface)840841flux = flux_parabolic(u_ll, u_rr, gradient_or_divergence,842equations_parabolic, parabolic_scheme)843844# Copy flux to left and right element storage845set_node_vars!(destination, flux, equations_parabolic, dg, i, j)846end847848return nothing849end850851@inline function mortar_fluxes_to_elements!(surface_flux_values,852mesh::TreeMesh{3},853equations_parabolic::AbstractEquationsParabolic,854mortar_l2::LobattoLegendreMortarL2,855dg::DGSEM, cache,856mortar,857fstar_upper_left, fstar_upper_right,858fstar_lower_left, fstar_lower_right,859fstar_tmp1)860lower_left_element = cache.mortars.neighbor_ids[1, mortar]861lower_right_element = cache.mortars.neighbor_ids[2, mortar]862upper_left_element = cache.mortars.neighbor_ids[3, mortar]863upper_right_element = cache.mortars.neighbor_ids[4, mortar]864large_element = cache.mortars.neighbor_ids[5, mortar]865866# Copy flux small to small867if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side868if cache.mortars.orientations[mortar] == 1869# L2 mortars in x-direction870direction = 1871elseif cache.mortars.orientations[mortar] == 2872# L2 mortars in y-direction873direction = 3874else # if cache.mortars.orientations[mortar] == 3875# L2 mortars in z-direction876direction = 5877end878else # large_sides[mortar] == 2 -> small elements on left side879if cache.mortars.orientations[mortar] == 1880# L2 mortars in x-direction881direction = 2882elseif cache.mortars.orientations[mortar] == 2883# L2 mortars in y-direction884direction = 4885else # if cache.mortars.orientations[mortar] == 3886# L2 mortars in z-direction887direction = 6888end889end890surface_flux_values[:, :, :, direction, upper_left_element] .= fstar_upper_left891surface_flux_values[:, :, :, direction, upper_right_element] .= fstar_upper_right892surface_flux_values[:, :, :, direction, lower_left_element] .= fstar_lower_left893surface_flux_values[:, :, :, direction, lower_right_element] .= fstar_lower_right894895# Project small fluxes to large element896if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side897if cache.mortars.orientations[mortar] == 1898# L2 mortars in x-direction899direction = 2900elseif cache.mortars.orientations[mortar] == 2901# L2 mortars in y-direction902direction = 4903else # if cache.mortars.orientations[mortar] == 3904# L2 mortars in z-direction905direction = 6906end907else # large_sides[mortar] == 2 -> small elements on left side908if cache.mortars.orientations[mortar] == 1909# L2 mortars in x-direction910direction = 1911elseif cache.mortars.orientations[mortar] == 2912# L2 mortars in y-direction913direction = 3914else # if cache.mortars.orientations[mortar] == 3915# L2 mortars in z-direction916direction = 5917end918end919920multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction,921large_element),922mortar_l2.reverse_lower, mortar_l2.reverse_upper,923fstar_upper_left, fstar_tmp1)924add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction,925large_element),926mortar_l2.reverse_upper, mortar_l2.reverse_upper,927fstar_upper_right, fstar_tmp1)928add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction,929large_element),930mortar_l2.reverse_lower, mortar_l2.reverse_lower,931fstar_lower_left, fstar_tmp1)932add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction,933large_element),934mortar_l2.reverse_upper, mortar_l2.reverse_lower,935fstar_lower_right, fstar_tmp1)936937return nothing938end939940function calc_volume_integral_gradient!(gradients, u_transformed,941mesh::TreeMesh{3}, # for dispatch only942equations_parabolic::AbstractEquationsParabolic,943dg::DGSEM, cache)944@unpack derivative_hat = dg.basis945gradients_x, gradients_y, gradients_z = gradients946947@threaded for element in eachelement(dg, cache)948# Calculate volume terms in one element,949# corresponds to `kernel` functions for the hyperbolic part of the flux950for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)951u_node = get_node_vars(u_transformed, equations_parabolic, dg,952i, j, k, element)953954for ii in eachnode(dg)955multiply_add_to_node_vars!(gradients_x, derivative_hat[ii, i],956u_node, equations_parabolic, dg,957ii, j, k, element)958end959960for jj in eachnode(dg)961multiply_add_to_node_vars!(gradients_y, derivative_hat[jj, j],962u_node, equations_parabolic, dg,963i, jj, k, element)964end965966for kk in eachnode(dg)967multiply_add_to_node_vars!(gradients_z, derivative_hat[kk, k],968u_node, equations_parabolic, dg,969i, j, kk, element)970end971end972end973974return nothing975end976977function calc_interface_flux_gradient!(surface_flux_values,978mesh::TreeMesh{3},979equations_parabolic,980dg::DG, parabolic_scheme, cache)981@unpack neighbor_ids, orientations = cache.interfaces982983@threaded for interface in eachinterface(dg, cache)984# Get neighboring elements985left_id = neighbor_ids[1, interface]986right_id = neighbor_ids[2, interface]987988# Determine interface direction with respect to elements:989# orientation = 1: left -> 2, right -> 1990# orientation = 2: left -> 4, right -> 3991# orientation = 3: left -> 6, right -> 5992left_direction = 2 * orientations[interface]993right_direction = 2 * orientations[interface] - 1994995for j in eachnode(dg), i in eachnode(dg)996# Call pointwise Riemann solver997u_ll, u_rr = get_surface_node_vars(cache.interfaces.u,998equations_parabolic, dg,999i, j, interface)10001001flux = flux_parabolic(u_ll, u_rr, Gradient(),1002equations_parabolic, parabolic_scheme)10031004# Copy flux to left and right element storage1005for v in eachvariable(equations_parabolic)1006surface_flux_values[v, i, j, left_direction, left_id] = flux[v]1007surface_flux_values[v, i, j, right_direction, right_id] = flux[v]1008end1009end1010end10111012return nothing1013end10141015function calc_surface_integral_gradient!(gradients,1016mesh::TreeMesh{3}, # for dispatch only1017equations_parabolic::AbstractEquationsParabolic,1018dg::DGSEM, cache)1019@unpack inverse_weights = dg.basis1020@unpack surface_flux_values = cache.elements10211022gradients_x, gradients_y, gradients_z = gradients10231024# Note that all fluxes have been computed with outward-pointing normal vectors.1025# We also use explicit assignments instead of `+=` to let `@muladd` turn these1026# into FMAs (see comment at the top of the file).1027factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±11028@threaded for element in eachelement(dg, cache)1029for m in eachnode(dg), l in eachnode(dg)1030for v in eachvariable(equations_parabolic)1031# surface at -x1032gradients_x[v, 1, l, m, element] = (gradients_x[v,10331,1034l,1035m,1036element] -1037surface_flux_values[v,1038l,1039m,10401,1041element] *1042factor)10431044# surface at +x1045gradients_x[v, nnodes(dg), l, m, element] = (gradients_x[v,1046nnodes(dg),1047l,1048m,1049element] +1050surface_flux_values[v,1051l,1052m,10532,1054element] *1055factor)10561057# surface at -y1058gradients_y[v, l, 1, m, element] = (gradients_y[v,1059l,10601,1061m,1062element] -1063surface_flux_values[v,1064l,1065m,10663,1067element] *1068factor)10691070# surface at +y1071gradients_y[v, l, nnodes(dg), m, element] = (gradients_y[v,1072l,1073nnodes(dg),1074m,1075element] +1076surface_flux_values[v,1077l,1078m,10794,1080element] *1081factor)10821083# surface at -z1084gradients_z[v, l, m, 1, element] = (gradients_z[v,1085l,1086m,10871,1088element] -1089surface_flux_values[v,1090l,1091m,10925,1093element] *1094factor)10951096# surface at +z1097gradients_z[v, l, m, nnodes(dg), element] = (gradients_z[v,1098l,1099m,1100nnodes(dg),1101element] +1102surface_flux_values[v,1103l,1104m,11056,1106element] *1107factor)1108end1109end1110end11111112return nothing1113end11141115function apply_jacobian_parabolic!(gradients::NTuple{3}, mesh::AbstractMesh{3},1116equations_parabolic::AbstractEquationsParabolic,1117dg::DG, cache)1118gradients_x, gradients_y, gradients_z = gradients11191120apply_jacobian_parabolic!(gradients_x, mesh, equations_parabolic, dg,1121cache)1122apply_jacobian_parabolic!(gradients_y, mesh, equations_parabolic, dg,1123cache)1124apply_jacobian_parabolic!(gradients_z, mesh, equations_parabolic, dg,1125cache)11261127return nothing1128end11291130# Needed to *not* flip the sign of the inverse Jacobian.1131# This is because the parabolic fluxes are assumed to be of the form1132# `du/dt + df/dx = dg/dx + source(x,t)`,1133# where f(u) is the inviscid flux and g(u) is the parabolic flux.1134function apply_jacobian_parabolic!(du::AbstractArray, mesh::TreeMesh{3},1135equations_parabolic::AbstractEquationsParabolic,1136dg::DG, cache)1137@unpack inverse_jacobian = cache.elements11381139@threaded for element in eachelement(dg, cache)1140factor = inverse_jacobian[element]11411142for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)1143for v in eachvariable(equations_parabolic)1144du[v, i, j, k, element] *= factor1145end1146end1147end11481149return nothing1150end11511152# Need dimension specific version to avoid error at dispatching1153function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic::Nothing,1154equations_parabolic::AbstractEquations{3}, dg::DG,1155cache)1156return nothing1157end11581159function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,1160equations_parabolic::AbstractEquations{3}, dg::DG,1161cache)1162@unpack node_coordinates = cache.elements11631164@threaded for element in eachelement(dg, cache)1165for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)1166u_local = get_node_vars(u, equations_parabolic, dg, i, j, k, element)1167gradients_x_local = get_node_vars(gradients[1], equations_parabolic, dg,1168i, j, k, element)1169gradients_y_local = get_node_vars(gradients[2], equations_parabolic, dg,1170i, j, k, element)1171gradients_z_local = get_node_vars(gradients[3], equations_parabolic, dg,1172i, j, k, element)1173gradients_local = (gradients_x_local, gradients_y_local, gradients_z_local)1174x_local = get_node_coords(node_coordinates, equations_parabolic, dg,1175i, j, k, element)1176du_local = source_terms_parabolic(u_local, gradients_local, x_local, t,1177equations_parabolic)1178add_to_node_vars!(du, du_local, equations_parabolic, dg, i, j, k, element)1179end1180end11811182return nothing1183end1184end # @muladd118511861187