Path: blob/main/src/solvers/dgsem_tree/dg_2d_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{2}, P4estMesh{2}},11equations_hyperbolic::AbstractEquations,12dg::DG, n_elements, uEltype)13parabolic_container = init_parabolic_container_2d(nvariables(equations_hyperbolic),14nnodes(dg), n_elements,15uEltype)1617cache_parabolic = (; parabolic_container)1819return cache_parabolic20end2122# This file collects all methods that have been updated to work with parabolic systems of equations23#24# assumptions: parabolic terms are of the form div(f(u, grad(u))) and25# will be discretized first order form as follows:26# 1. compute grad(u)27# 2. compute f(u, grad(u))28# 3. compute div(f(u, grad(u))) (i.e., the "regular" rhs! call)29# boundary conditions will be applied to both grad(u) and div(f(u, grad(u))).30function rhs_parabolic!(du, u, t, mesh::Union{TreeMesh{2}, TreeMesh{3}},31equations_parabolic::AbstractEquationsParabolic,32boundary_conditions_parabolic, source_terms_parabolic,33dg::DG, parabolic_scheme, cache, cache_parabolic)34@unpack parabolic_container = cache_parabolic35@unpack u_transformed, gradients, flux_parabolic = parabolic_container3637# Convert conservative variables to a form more suitable for parabolic flux calculations38@trixi_timeit timer() "transform variables" begin39transform_variables!(u_transformed, u, mesh, equations_parabolic,40dg, cache)41end4243# Compute the gradients of the transformed variables44@trixi_timeit timer() "calculate gradient" begin45calc_gradient!(gradients, u_transformed, t, mesh,46equations_parabolic, boundary_conditions_parabolic,47dg, parabolic_scheme, cache)48end4950# Compute and store the parabolic fluxes51@trixi_timeit timer() "calculate parabolic fluxes" begin52calc_parabolic_fluxes!(flux_parabolic, gradients, u_transformed, mesh,53equations_parabolic, dg, cache)54end5556# The remainder of this function is essentially a regular rhs! for parabolic57# equations (i.e., it computes the divergence of the parabolic fluxes)58#59# OBS! In `calc_parabolic_fluxes!`, the parabolic flux values at the volume nodes of each element have60# been computed and stored in `flux_parabolic`. In the following, we *reuse* (abuse) the61# `interfaces` and `boundaries` containers in `cache` to interpolate and store the62# *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it63# is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *parabolic flux values*64# and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we65# do not need to recreate the existing data structure only with a different name, and c) we do not66# need to interpolate solutions *and* gradients to the surfaces.6768# Reset du69@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)7071# Calculate volume integral.72# This calls the specialized version for the parabolic fluxes from73# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.74@trixi_timeit timer() "volume integral" begin75calc_volume_integral!(du, flux_parabolic, mesh, equations_parabolic, dg, cache)76end7778# Prolong solution to interfaces.79# This calls the specialized version for the parabolic fluxes from80# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.81@trixi_timeit timer() "prolong2interfaces" begin82prolong2interfaces!(cache, flux_parabolic, mesh, equations_parabolic, dg)83end8485# Calculate interface fluxes86# This calls the specialized version for the parabolic fluxes from87# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.88@trixi_timeit timer() "interface flux" begin89calc_interface_flux!(cache.elements.surface_flux_values,90mesh, equations_parabolic, dg,91parabolic_scheme, cache)92end9394# Prolong parabolic fluxes to boundaries.95# This calls the specialized version for the parabolic fluxes from96# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.97@trixi_timeit timer() "prolong2boundaries" begin98prolong2boundaries!(cache, flux_parabolic, mesh, equations_parabolic, dg)99end100101# Calculate boundary fluxes.102# This calls the specialized version for parabolic equations.103@trixi_timeit timer() "boundary flux" begin104calc_boundary_flux_divergence!(cache, t,105boundary_conditions_parabolic, mesh,106equations_parabolic,107dg.surface_integral, dg)108end109110# Prolong parabolic fluxes to mortars.111# This calls the specialized version for the parabolic fluxes from112# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.113@trixi_timeit timer() "prolong2mortars" begin114prolong2mortars!(cache, flux_parabolic, mesh, equations_parabolic,115dg.mortar, dg)116end117118# Calculate mortar fluxes.119# This calls the specialized version from120# `dg_2d_parabolic.jl` or `dg_3d_parabolic.jl`.121@trixi_timeit timer() "mortar flux" begin122calc_mortar_flux!(cache.elements.surface_flux_values,123mesh, equations_parabolic, dg.mortar, dg.surface_integral,124dg, parabolic_scheme, Divergence(), cache)125end126127# Calculate surface integrals.128# This reuses `calc_surface_integral!` for the purely hyperbolic case.129@trixi_timeit timer() "surface integral" begin130calc_surface_integral!(nothing, du, u, mesh, equations_parabolic,131dg.surface_integral, dg, cache)132end133134# Apply Jacobian from mapping to reference element135@trixi_timeit timer() "Jacobian" begin136apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache)137end138139@trixi_timeit timer() "source terms parabolic" begin140calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,141equations_parabolic, dg, cache)142end143144return nothing145end146147# Transform solution variables prior to taking the gradient148# (e.g., conservative to primitive variables). Defaults to doing nothing.149# TODO: can we avoid copying data?150function transform_variables!(u_transformed, u, mesh::Union{TreeMesh{2}, P4estMesh{2}},151equations_parabolic::AbstractEquationsParabolic,152dg::DG, cache)153transformation = gradient_variable_transformation(equations_parabolic)154155@threaded for element in eachelement(dg, cache)156# Calculate volume terms in one element157for j in eachnode(dg), i in eachnode(dg)158u_node = get_node_vars(u, equations_parabolic, dg, i, j, element)159u_transformed_node = transformation(u_node, equations_parabolic)160set_node_vars!(u_transformed, u_transformed_node, equations_parabolic, dg,161i, j, element)162end163end164165return nothing166end167168# This is the version used when calculating the divergence of the parabolic fluxes.169# Identical to weak-form volume integral/kernel for the purely hyperbolic case,170# except that the fluxes are here already precomputed in `calc_parabolic_fluxes!`171function calc_volume_integral!(du, flux_parabolic, mesh::TreeMesh{2},172equations_parabolic::AbstractEquationsParabolic,173dg::DGSEM, cache)174@unpack derivative_hat = dg.basis175flux_parabolic_x, flux_parabolic_y = flux_parabolic176177@threaded for element in eachelement(dg, cache)178# Calculate volume terms in one element179for j in eachnode(dg), i in eachnode(dg)180flux_1_node = get_node_vars(flux_parabolic_x, equations_parabolic, dg,181i, j, element)182flux_2_node = get_node_vars(flux_parabolic_y, equations_parabolic, dg,183i, j, element)184185for ii in eachnode(dg)186multiply_add_to_node_vars!(du, derivative_hat[ii, i], flux_1_node,187equations_parabolic, dg, ii, j, element)188end189190for jj in eachnode(dg)191multiply_add_to_node_vars!(du, derivative_hat[jj, j], flux_2_node,192equations_parabolic, dg, i, jj, element)193end194end195end196197return nothing198end199200# This is the version used when calculating the divergence of the parabolic fluxes.201# Specialization `flux_parabolic::Tuple` needed to202# avoid amibiguity with the hyperbolic version of `prolong2interfaces!` in dg_2d.jl203# which is for the variables itself, i.e., `u::Array{uEltype, 4}`.204function prolong2interfaces!(cache, flux_parabolic::Tuple,205mesh::TreeMesh{2},206equations_parabolic::AbstractEquationsParabolic,207dg::DG)208@unpack interfaces = cache209@unpack orientations, neighbor_ids = interfaces210211# OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*!212interfaces_u = interfaces.u213214flux_parabolic_x, flux_parabolic_y = flux_parabolic215216@threaded for interface in eachinterface(dg, cache)217left_element = neighbor_ids[1, interface]218right_element = neighbor_ids[2, interface]219220if orientations[interface] == 1221# interface in x-direction222for j in eachnode(dg), v in eachvariable(equations_parabolic)223interfaces_u[1, v, j, interface] = flux_parabolic_x[v, nnodes(dg), j,224left_element]225interfaces_u[2, v, j, interface] = flux_parabolic_x[v, 1, j,226right_element]227end228else # if orientations[interface] == 2229# interface in y-direction230for i in eachnode(dg), v in eachvariable(equations_parabolic)231interfaces_u[1, v, i, interface] = flux_parabolic_y[v, i, nnodes(dg),232left_element]233interfaces_u[2, v, i, interface] = flux_parabolic_y[v, i, 1,234right_element]235end236end237end238239return nothing240end241242# This is the version used when calculating the divergence of the parabolic fluxes.243# Specialization `flux_parabolic::Tuple` needed to244# avoid amibiguity with the hyperbolic version of `prolong2interfaces!` in dg_2d.jl245# which is for the variables itself, i.e., `u::Array{uEltype, 4}`.246function prolong2interfaces!(cache, flux_parabolic::Tuple,247mesh::TreeMesh{2},248equations_parabolic::AbstractEquationsParabolic,249dg::DGSEM{<:GaussLegendreBasis})250@unpack interfaces = cache251@unpack orientations, neighbor_ids = interfaces252@unpack boundary_interpolation = dg.basis253254# OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*!255interfaces_u = interfaces.u256257flux_parabolic_x, flux_parabolic_y = flux_parabolic258259@threaded for interface in eachinterface(dg, cache)260left_element = neighbor_ids[1, interface]261right_element = neighbor_ids[2, interface]262263if orientations[interface] == 1264# interface in x-direction265for j in eachnode(dg)266for v in eachvariable(equations_parabolic)267# Interpolate to the interfaces using a local variable for268# the accumulation of values (to reduce global memory operations).269interface_u_1 = zero(eltype(interfaces_u))270interface_u_2 = zero(eltype(interfaces_u))271for ii in eachnode(dg)272# Not += to allow `@muladd` to turn these into FMAs273# (see comment at the top of the file)274# Need `boundary_interpolation` at right (+1) node for left element275interface_u_1 = (interface_u_1 +276flux_parabolic_x[v, ii, j, left_element] *277boundary_interpolation[ii, 2])278# Need `boundary_interpolation` at left (-1) node for right element279interface_u_2 = (interface_u_2 +280flux_parabolic_x[v, ii, j, right_element] *281boundary_interpolation[ii, 1])282end283interfaces_u[1, v, j, interface] = interface_u_1284interfaces_u[2, v, j, interface] = interface_u_2285end286end287else # if orientations[interface] == 2288# interface in y-direction289for i in eachnode(dg)290for v in eachvariable(equations_parabolic)291interface_u_1 = zero(eltype(interfaces_u))292interface_u_2 = zero(eltype(interfaces_u))293for jj in eachnode(dg)294# Need `boundary_interpolation` at right (+1) node for left element295interface_u_1 = (interface_u_1 +296flux_parabolic_y[v, i, jj, left_element] *297boundary_interpolation[jj, 2])298# Need `boundary_interpolation` at left (-1) node for right element299interface_u_2 = (interface_u_2 +300flux_parabolic_y[v, i, jj, right_element] *301boundary_interpolation[jj, 1])302end303interfaces_u[1, v, i, interface] = interface_u_1304interfaces_u[2, v, i, interface] = interface_u_2305end306end307end308end309310return nothing311end312313# This is the version used when calculating the divergence of the parabolic fluxes314function calc_interface_flux!(surface_flux_values, mesh::TreeMesh{2},315equations_parabolic, dg::DG, parabolic_scheme,316cache)317@unpack neighbor_ids, orientations = cache.interfaces318319@threaded for interface in eachinterface(dg, cache)320# Get neighboring elements321left_id = neighbor_ids[1, interface]322right_id = neighbor_ids[2, interface]323324# Determine interface direction with respect to elements:325# orientation = 1: left -> 2, right -> 1326# orientation = 2: left -> 4, right -> 3327left_direction = 2 * orientations[interface]328right_direction = 2 * orientations[interface] - 1329330for i in eachnode(dg)331# Get precomputed fluxes at interfaces332flux_ll, flux_rr = get_surface_node_vars(cache.interfaces.u,333equations_parabolic,334dg, i, interface)335336flux = flux_parabolic(flux_ll, flux_rr, Divergence(),337equations_parabolic, parabolic_scheme)338339# Copy flux to left and right element storage340for v in eachvariable(equations_parabolic)341surface_flux_values[v, i, left_direction, left_id] = flux[v]342surface_flux_values[v, i, right_direction, right_id] = flux[v]343end344end345end346347return nothing348end349350# This is the version used when calculating the divergence of the parabolic fluxes.351# Specialization `flux_parabolic::Tuple` needed to352# avoid amibiguity with the hyperbolic version of `prolong2boundaries!` in dg_2d.jl353# which is for the variables itself, i.e., `u::Array{uEltype, 4}`.354function prolong2boundaries!(cache, flux_parabolic::Tuple,355mesh::TreeMesh{2},356equations_parabolic::AbstractEquationsParabolic,357dg::DG)358@unpack boundaries = cache359@unpack orientations, neighbor_sides, neighbor_ids = boundaries360361# OBS! `boundaries_u` stores the "interpolated" *fluxes* and *not the solution*!362boundaries_u = boundaries.u363flux_parabolic_x, flux_parabolic_y = flux_parabolic364365@threaded for boundary in eachboundary(dg, cache)366element = neighbor_ids[boundary]367368if orientations[boundary] == 1369# boundary in x-direction370if neighbor_sides[boundary] == 1371# element in -x direction of boundary372for l in eachnode(dg), v in eachvariable(equations_parabolic)373boundaries_u[1, v, l, boundary] = flux_parabolic_x[v, nnodes(dg), l,374element]375end376else # Element in +x direction of boundary377for l in eachnode(dg), v in eachvariable(equations_parabolic)378boundaries_u[2, v, l, boundary] = flux_parabolic_x[v, 1, l,379element]380end381end382else # if orientations[boundary] == 2383# boundary in y-direction384if neighbor_sides[boundary] == 1385# element in -y direction of boundary386for l in eachnode(dg), v in eachvariable(equations_parabolic)387boundaries_u[1, v, l, boundary] = flux_parabolic_y[v, l, nnodes(dg),388element]389end390else391# element in +y direction of boundary392for l in eachnode(dg), v in eachvariable(equations_parabolic)393boundaries_u[2, v, l, boundary] = flux_parabolic_y[v, l, 1,394element]395end396end397end398end399400return nothing401end402403# This is the version used when calculating the divergence of the parabolic fluxes.404# Specialization `flux_parabolic::Tuple` needed to405# avoid amibiguity with the hyperbolic version of `prolong2boundaries!` in dg_2d.jl406# which is for the variables itself, i.e., `u::Array{uEltype, 4}`.407function prolong2boundaries!(cache, flux_parabolic::Tuple,408mesh::TreeMesh{2},409equations_parabolic::AbstractEquationsParabolic,410dg::DGSEM{<:GaussLegendreBasis})411@unpack boundaries = cache412@unpack orientations, neighbor_sides, neighbor_ids = boundaries413@unpack boundary_interpolation = dg.basis414415# OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*!416boundaries_u = boundaries.u417flux_parabolic_x, flux_parabolic_y = flux_parabolic418419@threaded for boundary in eachboundary(dg, cache)420element = neighbor_ids[boundary]421422if orientations[boundary] == 1423# boundary in x-direction424if neighbor_sides[boundary] == 1425# element in -x direction of boundary => interpolate to right boundary node (+1)426for l in eachnode(dg)427for v in eachvariable(equations_parabolic)428# Interpolate to the boundaries using a local variable for429# the accumulation of values (to reduce global memory operations).430boundary_u = zero(eltype(boundaries_u))431for ii in eachnode(dg)432# Not += to allow `@muladd` to turn these into FMAs433# (see comment at the top of the file)434boundary_u = (boundary_u +435flux_parabolic_x[v, ii, l, element] *436boundary_interpolation[ii, 2])437end438boundaries_u[1, v, l, boundary] = boundary_u439end440end441else # element in +x direction of boundary => interpolate to left boundary node (-1)442for l in eachnode(dg)443for v in eachvariable(equations_parabolic)444boundary_u = zero(eltype(boundaries_u))445for ii in eachnode(dg)446boundary_u = (boundary_u +447flux_parabolic_x[v, ii, l, element] *448boundary_interpolation[ii, 1])449end450boundaries_u[2, v, l, boundary] = boundary_u451end452end453end454else # if orientations[boundary] == 2455# boundary in y-direction456if neighbor_sides[boundary] == 1457# element in -y direction of boundary => interpolate to right boundary node (+1)458for l in eachnode(dg)459for v in eachvariable(equations_parabolic)460boundary_u = zero(eltype(boundaries_u))461for jj in eachnode(dg)462boundary_u = (boundary_u +463flux_parabolic_y[v, l, jj, element] *464boundary_interpolation[jj, 2])465end466boundaries_u[1, v, l, boundary] = boundary_u467end468end469else # element in +y direction of boundary => interpolate to left boundary node (-1)470for l in eachnode(dg)471for v in eachvariable(equations_parabolic)472boundary_u = zero(eltype(boundaries_u))473for jj in eachnode(dg)474boundary_u = (boundary_u +475flux_parabolic_y[v, l, jj, element] *476boundary_interpolation[jj, 1])477end478boundaries_u[2, v, l, boundary] = boundary_u479end480end481end482end483end484485return nothing486end487488function calc_parabolic_fluxes!(flux_parabolic,489gradients, u_transformed,490mesh::Union{TreeMesh{2}, P4estMesh{2}},491equations_parabolic::AbstractEquationsParabolic,492dg::DG, cache)493gradients_x, gradients_y = gradients494flux_parabolic_x, flux_parabolic_y = flux_parabolic # output arrays495496@threaded for element in eachelement(dg, cache)497for j in eachnode(dg), i in eachnode(dg)498# Get solution and gradients499u_node = get_node_vars(u_transformed, equations_parabolic, dg,500i, j, element)501gradients_1_node = get_node_vars(gradients_x, equations_parabolic, dg,502i, j, element)503gradients_2_node = get_node_vars(gradients_y, equations_parabolic, dg,504i, j, element)505506# Calculate parabolic flux and store each component for later use507flux_parabolic_node_x = flux(u_node, (gradients_1_node, gradients_2_node),5081, equations_parabolic)509flux_parabolic_node_y = flux(u_node, (gradients_1_node, gradients_2_node),5102, equations_parabolic)511set_node_vars!(flux_parabolic_x, flux_parabolic_node_x,512equations_parabolic, dg,513i, j, element)514set_node_vars!(flux_parabolic_y, flux_parabolic_node_y,515equations_parabolic, dg,516i, j, element)517end518end519520return nothing521end522523# TODO: parabolic; decide if we should keep this, and if so, extend to 3D.524function get_unsigned_normal_vector_2d(direction)525if direction > 4 || direction < 1526error("Direction = $direction; in 2D, direction should be 1, 2, 3, or 4.")527end528if direction == 1 || direction == 2529return SVector(1.0, 0.0)530else531return SVector(0.0, 1.0)532end533end534535function calc_boundary_flux_gradient!(cache, t,536boundary_conditions_parabolic::BoundaryConditionPeriodic,537mesh::Union{TreeMesh{2}, P4estMesh{2}},538equations_parabolic::AbstractEquationsParabolic,539surface_integral, dg::DG)540return nothing541end542543function calc_boundary_flux_divergence!(cache, t,544boundary_conditions_parabolic::BoundaryConditionPeriodic,545mesh::Union{TreeMesh{2}, P4estMesh{2}},546equations_parabolic::AbstractEquationsParabolic,547surface_integral, dg::DG)548return nothing549end550551function calc_boundary_flux_gradient!(cache, t,552boundary_conditions_parabolic::NamedTuple,553mesh::TreeMesh{2}, # for dispatch only554equations_parabolic::AbstractEquationsParabolic,555surface_integral, dg::DG)556@unpack surface_flux_values = cache.elements557@unpack n_boundaries_per_direction = cache.boundaries558559# Calculate indices560lasts = accumulate(+, n_boundaries_per_direction)561firsts = lasts - n_boundaries_per_direction .+ 1562563# Calc boundary fluxes in each direction564calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,565boundary_conditions_parabolic[1],566equations_parabolic, surface_integral,567dg, cache,5681, firsts[1], lasts[1])569calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,570boundary_conditions_parabolic[2],571equations_parabolic, surface_integral,572dg, cache,5732, firsts[2], lasts[2])574calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,575boundary_conditions_parabolic[3],576equations_parabolic, surface_integral,577dg, cache,5783, firsts[3], lasts[3])579calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,580boundary_conditions_parabolic[4],581equations_parabolic, surface_integral,582dg, cache,5834, firsts[4], lasts[4])584585return nothing586end587588function calc_boundary_flux_by_direction_gradient!(surface_flux_values::AbstractArray{<:Any,5894},590t, boundary_condition,591equations_parabolic::AbstractEquationsParabolic,592surface_integral, dg::DG, cache,593direction, first_boundary,594last_boundary)595@unpack surface_flux = surface_integral596@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries597598@threaded for boundary in first_boundary:last_boundary599# Get neighboring element600neighbor = neighbor_ids[boundary]601602for i in eachnode(dg)603# Get boundary flux604u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, i, boundary)605if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right606u_inner = u_ll607else # Element is on the right, boundary on the left608u_inner = u_rr609end610611# TODO: revisit if we want more general boundary treatments.612# This assumes the gradient numerical flux at the boundary is the gradient variable,613# which is consistent with BR1, LDG.614flux_inner = u_inner615616x = get_node_coords(node_coordinates, equations_parabolic, dg, i, boundary)617flux = boundary_condition(flux_inner, u_inner,618get_unsigned_normal_vector_2d(direction),619x, t, Gradient(), equations_parabolic)620621# Copy flux to left and right element storage622for v in eachvariable(equations_parabolic)623surface_flux_values[v, i, direction, neighbor] = flux[v]624end625end626end627628return nothing629end630631function calc_boundary_flux_divergence!(cache, t,632boundary_conditions_parabolic::NamedTuple,633mesh::TreeMesh{2},634equations_parabolic::AbstractEquationsParabolic,635surface_integral, dg::DG)636@unpack surface_flux_values = cache.elements637@unpack n_boundaries_per_direction = cache.boundaries638639# Calculate indices640lasts = accumulate(+, n_boundaries_per_direction)641firsts = lasts - n_boundaries_per_direction .+ 1642643# Calc boundary fluxes in each direction644calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,645boundary_conditions_parabolic[1],646equations_parabolic, surface_integral,647dg, cache,6481, firsts[1], lasts[1])649calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,650boundary_conditions_parabolic[2],651equations_parabolic, surface_integral,652dg, cache,6532, firsts[2], lasts[2])654calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,655boundary_conditions_parabolic[3],656equations_parabolic, surface_integral,657dg, cache,6583, firsts[3], lasts[3])659calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,660boundary_conditions_parabolic[4],661equations_parabolic, surface_integral,662dg, cache,6634, firsts[4], lasts[4])664665return nothing666end667function calc_boundary_flux_by_direction_divergence!(surface_flux_values::AbstractArray{<:Any,6684},669t,670boundary_condition,671equations_parabolic::AbstractEquationsParabolic,672surface_integral, dg::DG, cache,673direction, first_boundary,674last_boundary)675@unpack surface_flux = surface_integral676677# Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction")678# of the parabolic flux, as computed in `prolong2boundaries!`679@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries680681@threaded for boundary in first_boundary:last_boundary682# Get neighboring element683neighbor = neighbor_ids[boundary]684685for i in eachnode(dg)686# Get parabolic boundary fluxes687flux_ll, flux_rr = get_surface_node_vars(u, equations_parabolic, dg, i,688boundary)689if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right690flux_inner = flux_ll691else # Element is on the right, boundary on the left692flux_inner = flux_rr693end694695x = get_node_coords(node_coordinates, equations_parabolic, dg, i, boundary)696697# TODO: add a field in `cache.boundaries` for gradient information.698# Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information.699# This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion2D and700# NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion2D as of 2022-6-27.701# It will not work with implementations which utilize `u_inner` to impose boundary conditions, such as702# the `Slip` boundary condition, which can be imposed to realize reflective or symmetric boundaries.703flux = boundary_condition(flux_inner, nothing,704get_unsigned_normal_vector_2d(direction),705x, t, Divergence(), equations_parabolic)706707# Copy flux to left and right element storage708for v in eachvariable(equations_parabolic)709surface_flux_values[v, i, direction, neighbor] = flux[v]710end711end712end713714return nothing715end716717# Specialization `flux_parabolic::Tuple` needed to718# avoid amibiguity with the hyperbolic version of `prolong2mortars!` in dg_2d.jl719# which is for the variables itself, i.e., `u::Array{uEltype, 4}`.720function prolong2mortars!(cache, flux_parabolic::Tuple,721mesh::TreeMesh{2},722equations_parabolic::AbstractEquationsParabolic,723mortar_l2::LobattoLegendreMortarL2,724dg::DGSEM)725flux_parabolic_x, flux_parabolic_y = flux_parabolic726@threaded for mortar in eachmortar(dg, cache)727large_element = cache.mortars.neighbor_ids[3, mortar]728upper_element = cache.mortars.neighbor_ids[2, mortar]729lower_element = cache.mortars.neighbor_ids[1, mortar]730731# Copy solution small to small732if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side733if cache.mortars.orientations[mortar] == 1734# L2 mortars in x-direction735for l in eachnode(dg)736for v in eachvariable(equations_parabolic)737cache.mortars.u_upper[2, v, l, mortar] = flux_parabolic_x[v,7381,739l,740upper_element]741cache.mortars.u_lower[2, v, l, mortar] = flux_parabolic_x[v,7421,743l,744lower_element]745end746end747else748# L2 mortars in y-direction749for l in eachnode(dg)750for v in eachvariable(equations_parabolic)751cache.mortars.u_upper[2, v, l, mortar] = flux_parabolic_y[v,752l,7531,754upper_element]755cache.mortars.u_lower[2, v, l, mortar] = flux_parabolic_y[v,756l,7571,758lower_element]759end760end761end762else # large_sides[mortar] == 2 -> small elements on left side763if cache.mortars.orientations[mortar] == 1764# L2 mortars in x-direction765for l in eachnode(dg)766for v in eachvariable(equations_parabolic)767cache.mortars.u_upper[1, v, l, mortar] = flux_parabolic_x[v,768nnodes(dg),769l,770upper_element]771cache.mortars.u_lower[1, v, l, mortar] = flux_parabolic_x[v,772nnodes(dg),773l,774lower_element]775end776end777else778# L2 mortars in y-direction779for l in eachnode(dg)780for v in eachvariable(equations_parabolic)781cache.mortars.u_upper[1, v, l, mortar] = flux_parabolic_y[v,782l,783nnodes(dg),784upper_element]785cache.mortars.u_lower[1, v, l, mortar] = flux_parabolic_y[v,786l,787nnodes(dg),788lower_element]789end790end791end792end793794# Interpolate large element face data to small interface locations795if cache.mortars.large_sides[mortar] == 1 # -> large element on left side796leftright = 1797if cache.mortars.orientations[mortar] == 1798# L2 mortars in x-direction799u_large = view(flux_parabolic_x, :, nnodes(dg), :, large_element)800element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,801mortar, u_large)802else803# L2 mortars in y-direction804u_large = view(flux_parabolic_y, :, :, nnodes(dg), large_element)805element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,806mortar, u_large)807end808else # large_sides[mortar] == 2 -> large element on right side809leftright = 2810if cache.mortars.orientations[mortar] == 1811# L2 mortars in x-direction812u_large = view(flux_parabolic_x, :, 1, :, large_element)813element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,814mortar, u_large)815else816# L2 mortars in y-direction817u_large = view(flux_parabolic_y, :, :, 1, large_element)818element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,819mortar, u_large)820end821end822end823824return nothing825end826827# NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms.828# Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for829# hyperbolic terms with conserved terms only, i.e., no nonconservative terms.830function calc_mortar_flux!(surface_flux_values, mesh::TreeMesh{2},831equations_parabolic::AbstractEquationsParabolic,832mortar_l2::LobattoLegendreMortarL2,833surface_integral, dg::DG,834parabolic_scheme, gradient_or_divergence,835cache)836@unpack surface_flux = surface_integral837@unpack u_lower, u_upper, orientations = cache.mortars838@unpack fstar_primary_upper_threaded, fstar_primary_lower_threaded = cache839840@threaded for mortar in eachmortar(dg, cache)841# Choose thread-specific pre-allocated container842fstar_upper = fstar_primary_upper_threaded[Threads.threadid()]843fstar_lower = fstar_primary_lower_threaded[Threads.threadid()]844845# Calculate fluxes846orientation = orientations[mortar]847calc_fstar!(fstar_upper, mesh, equations_parabolic, surface_flux,848dg, parabolic_scheme, gradient_or_divergence,849u_upper, mortar, orientation)850calc_fstar!(fstar_lower, mesh, equations_parabolic, surface_flux,851dg, parabolic_scheme, gradient_or_divergence,852u_lower, mortar, orientation)853854mortar_fluxes_to_elements!(surface_flux_values,855mesh, equations_parabolic, mortar_l2, dg, cache,856mortar, fstar_upper, fstar_lower)857end858859return nothing860end861862# For Gauss-Legendre DGSEM mortars are not yet implemented863function calc_mortar_flux!(surface_flux_values, mesh::TreeMesh{2},864equations_parabolic::AbstractEquationsParabolic,865mortar::Nothing,866surface_integral, dg::DGSEM{<:GaussLegendreBasis},867parabolic_scheme, gradient_or_divergence,868cache)869@assert isempty(eachmortar(dg, cache))870return nothing871end872873@inline function calc_fstar!(destination::AbstractArray{<:Any, 2},874mesh, equations_parabolic::AbstractEquationsParabolic,875surface_flux, dg::DGSEM,876parabolic_scheme, gradient_or_divergence,877u_interfaces, interface, orientation)878for i in eachnode(dg)879# Call pointwise two-point numerical flux function880u_ll, u_rr = get_surface_node_vars(u_interfaces, equations_parabolic, dg, i,881interface)882883flux = flux_parabolic(u_ll, u_rr, gradient_or_divergence,884equations_parabolic, parabolic_scheme)885886# Copy flux to left and right element storage887set_node_vars!(destination, flux, equations_parabolic, dg, i)888end889890return nothing891end892893@inline function mortar_fluxes_to_elements!(surface_flux_values,894mesh::TreeMesh{2},895equations_parabolic::AbstractEquationsParabolic,896mortar_l2::LobattoLegendreMortarL2,897dg::DGSEM, cache,898mortar, fstar_upper, fstar_lower)899large_element = cache.mortars.neighbor_ids[3, mortar]900upper_element = cache.mortars.neighbor_ids[2, mortar]901lower_element = cache.mortars.neighbor_ids[1, mortar]902903# Copy flux small to small904if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side905if cache.mortars.orientations[mortar] == 1906# L2 mortars in x-direction907direction = 1908else909# L2 mortars in y-direction910direction = 3911end912else # large_sides[mortar] == 2 -> small elements on left side913if cache.mortars.orientations[mortar] == 1914# L2 mortars in x-direction915direction = 2916else917# L2 mortars in y-direction918direction = 4919end920end921surface_flux_values[:, :, direction, upper_element] .= fstar_upper922surface_flux_values[:, :, direction, lower_element] .= fstar_lower923924# Project small fluxes to large element925if cache.mortars.large_sides[mortar] == 1 # -> large element on left side926if cache.mortars.orientations[mortar] == 1927# L2 mortars in x-direction928direction = 2929else930# L2 mortars in y-direction931direction = 4932end933else # large_sides[mortar] == 2 -> large element on right side934if cache.mortars.orientations[mortar] == 1935# L2 mortars in x-direction936direction = 1937else938# L2 mortars in y-direction939direction = 3940end941end942943# TODO: Taal performance944# for v in eachvariable(equations)945# # The code below is semantically equivalent to946# # surface_flux_values[v, :, direction, large_element] .=947# # (mortar_l2.reverse_upper * fstar_upper[v, :] + mortar_l2.reverse_lower * fstar_lower[v, :])948# # but faster and does not allocate.949# # Note that `true * some_float == some_float` in Julia, i.e. `true` acts as950# # a universal `one`. Hence, the second `mul!` means "add the matrix-vector951# # product to the current value of the destination".952# @views mul!(surface_flux_values[v, :, direction, large_element],953# mortar_l2.reverse_upper, fstar_upper[v, :])954# @views mul!(surface_flux_values[v, :, direction, large_element],955# mortar_l2.reverse_lower, fstar_lower[v, :], true, true)956# end957# The code above could be replaced by the following code. However, the relative efficiency958# depends on the types of fstar_upper/fstar_lower and dg.l2mortar_reverse_upper.959# Using StaticArrays for both makes the code above faster for common test cases.960multiply_dimensionwise!(view(surface_flux_values, :, :, direction, large_element),961mortar_l2.reverse_upper, fstar_upper,962mortar_l2.reverse_lower, fstar_lower)963964return nothing965end966967function calc_volume_integral_gradient!(gradients, u_transformed,968mesh::TreeMesh{2}, # for dispatch only969equations_parabolic::AbstractEquationsParabolic,970dg::DGSEM, cache)971@unpack derivative_hat = dg.basis972gradients_x, gradients_y = gradients973974@threaded for element in eachelement(dg, cache)975# Calculate volume terms in one element,976# corresponds to `kernel` functions for the hyperbolic part of the flux977for j in eachnode(dg), i in eachnode(dg)978u_node = get_node_vars(u_transformed, equations_parabolic, dg,979i, j, element)980981for ii in eachnode(dg)982multiply_add_to_node_vars!(gradients_x, derivative_hat[ii, i],983u_node, equations_parabolic, dg,984ii, j, element)985end986987for jj in eachnode(dg)988multiply_add_to_node_vars!(gradients_y, derivative_hat[jj, j],989u_node, equations_parabolic, dg,990i, jj, element)991end992end993end994995return nothing996end997998function calc_interface_flux_gradient!(surface_flux_values,999mesh::TreeMesh{2},1000equations_parabolic,1001dg::DG, parabolic_scheme, cache)1002@unpack neighbor_ids, orientations = cache.interfaces10031004@threaded for interface in eachinterface(dg, cache)1005# Get neighboring elements1006left_id = neighbor_ids[1, interface]1007right_id = neighbor_ids[2, interface]10081009# Determine interface direction with respect to elements:1010# orientation = 1: left -> 2, right -> 11011# orientation = 2: left -> 4, right -> 31012left_direction = 2 * orientations[interface]1013right_direction = 2 * orientations[interface] - 110141015for i in eachnode(dg)1016# Call pointwise Riemann solver1017u_ll, u_rr = get_surface_node_vars(cache.interfaces.u,1018equations_parabolic, dg, i,1019interface)10201021flux = flux_parabolic(u_ll, u_rr, Gradient(),1022equations_parabolic, parabolic_scheme)1023# Copy flux to left and right element storage1024for v in eachvariable(equations_parabolic)1025surface_flux_values[v, i, left_direction, left_id] = flux[v]1026surface_flux_values[v, i, right_direction, right_id] = flux[v]1027end1028end1029end10301031return nothing1032end10331034function calc_surface_integral_gradient!(gradients,1035mesh::TreeMesh{2}, # for dispatch only1036equations_parabolic::AbstractEquationsParabolic,1037dg::DGSEM, cache)1038@unpack inverse_weights = dg.basis1039@unpack surface_flux_values = cache.elements10401041gradients_x, gradients_y = gradients10421043# Note that all fluxes have been computed with outward-pointing normal vectors.1044# We also use explicit assignments instead of `+=` to let `@muladd` turn these1045# into FMAs (see comment at the top of the file).1046factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±110471048@threaded for element in eachelement(dg, cache)1049for l in eachnode(dg)1050for v in eachvariable(equations_parabolic)1051# surface at -x1052gradients_x[v, 1, l, element] = (gradients_x[v,10531, l,1054element] -1055surface_flux_values[v,1056l, 1,1057element] *1058factor)10591060# surface at +x1061gradients_x[v, nnodes(dg), l, element] = (gradients_x[v,1062nnodes(dg), l,1063element] +1064surface_flux_values[v,1065l, 2,1066element] *1067factor)10681069# surface at -y1070gradients_y[v, l, 1, element] = (gradients_y[v,1071l, 1,1072element] -1073surface_flux_values[v,1074l, 3,1075element] *1076factor)10771078# surface at +y1079gradients_y[v, l, nnodes(dg), element] = (gradients_y[v,1080l, nnodes(dg),1081element] +1082surface_flux_values[v,1083l, 4,1084element] *1085factor)1086end1087end1088end10891090return nothing1091end10921093function calc_surface_integral_gradient!(gradients,1094mesh::TreeMesh{2}, # for dispatch only1095equations_parabolic::AbstractEquationsParabolic,1096dg::DGSEM{<:GaussLegendreBasis}, cache)1097@unpack boundary_interpolation_inverse_weights = dg.basis1098@unpack surface_flux_values = cache.elements10991100gradients_x, gradients_y = gradients11011102# Note that all fluxes have been computed with outward-pointing normal vectors.1103# We also use explicit assignments instead of `+=` to let `@muladd` turn these1104# into FMAs (see comment at the top of the file).1105@threaded for element in eachelement(dg, cache)1106for l in eachnode(dg)1107for v in eachvariable(equations_parabolic)1108# Aliases for repeatedly accessed variables1109surface_flux_minus = surface_flux_values[v, l, 1, element]1110surface_flux_plus = surface_flux_values[v, l, 2, element]1111for ii in eachnode(dg)1112# surface at -x1113gradients_x[v, ii, l, element] = (gradients_x[v, ii, l, element] -1114surface_flux_minus *1115boundary_interpolation_inverse_weights[ii,11161])11171118# surface at +x1119gradients_x[v, ii, l, element] = (gradients_x[v, ii, l, element] +1120surface_flux_plus *1121boundary_interpolation_inverse_weights[ii,11222])1123end11241125surface_flux_minus = surface_flux_values[v, l, 3, element]1126surface_flux_plus = surface_flux_values[v, l, 4, element]1127for jj in eachnode(dg)1128# surface at -y1129gradients_y[v, l, jj, element] = (gradients_y[v, l, jj, element] -1130surface_flux_minus *1131boundary_interpolation_inverse_weights[jj,11321])11331134# surface at +y1135gradients_y[v, l, jj, element] = (gradients_y[v, l, jj, element] +1136surface_flux_plus *1137boundary_interpolation_inverse_weights[jj,11382])1139end1140end1141end1142end11431144return nothing1145end11461147function reset_gradients!(gradients::NTuple{2}, dg::DG, cache)1148gradients_x, gradients_y = gradients11491150set_zero!(gradients_x, dg, cache)1151set_zero!(gradients_y, dg, cache)11521153return nothing1154end11551156# Calculate the gradient of the transformed variables1157function calc_gradient!(gradients, u_transformed, t,1158mesh::Union{TreeMesh{2}, TreeMesh{3}},1159equations_parabolic, boundary_conditions_parabolic,1160dg::DG, parabolic_scheme, cache)1161# Reset gradients1162@trixi_timeit timer() "reset gradients" begin1163reset_gradients!(gradients, dg, cache)1164end11651166# Calculate volume integral1167@trixi_timeit timer() "volume integral" begin1168calc_volume_integral_gradient!(gradients, u_transformed,1169mesh, equations_parabolic, dg, cache)1170end11711172# Prolong solution to interfaces1173# This reuses `prolong2interfaces!` for the purely hyperbolic case.1174@trixi_timeit timer() "prolong2interfaces" begin1175prolong2interfaces!(nothing, cache, u_transformed, mesh,1176equations_parabolic, dg)1177end11781179# Calculate interface fluxes1180@trixi_timeit timer() "interface flux" begin1181@unpack surface_flux_values = cache.elements1182calc_interface_flux_gradient!(surface_flux_values, mesh, equations_parabolic,1183dg, parabolic_scheme, cache)1184end11851186# Prolong solution to boundaries1187# This reuses `prolong2boundaries!` for the purely hyperbolic case.1188@trixi_timeit timer() "prolong2boundaries" begin1189prolong2boundaries!(cache, u_transformed, mesh, equations_parabolic, dg)1190end11911192# Calculate boundary fluxes1193@trixi_timeit timer() "boundary flux" begin1194calc_boundary_flux_gradient!(cache, t,1195boundary_conditions_parabolic, mesh,1196equations_parabolic,1197dg.surface_integral, dg)1198end11991200# Prolong solution to mortars.1201# This reuses `prolong2mortars` for the purely hyperbolic case.1202@trixi_timeit timer() "prolong2mortars" begin1203prolong2mortars!(cache, u_transformed, mesh, equations_parabolic,1204dg.mortar, dg)1205end12061207# Calculate mortar fluxes1208@trixi_timeit timer() "mortar flux" begin1209calc_mortar_flux!(surface_flux_values, mesh, equations_parabolic,1210dg.mortar, dg.surface_integral, dg,1211parabolic_scheme, Gradient(), cache)1212end12131214# Calculate surface integrals1215@trixi_timeit timer() "surface integral" begin1216calc_surface_integral_gradient!(gradients, mesh, equations_parabolic,1217dg, cache)1218end12191220# Apply Jacobian from mapping to reference element1221@trixi_timeit timer() "Jacobian" begin1222apply_jacobian_parabolic!(gradients, mesh, equations_parabolic, dg,1223cache)1224end12251226return nothing1227end12281229function apply_jacobian_parabolic!(gradients::NTuple{2}, mesh::AbstractMesh{2},1230equations_parabolic::AbstractEquationsParabolic,1231dg::DG, cache)1232gradients_x, gradients_y = gradients12331234apply_jacobian_parabolic!(gradients_x, mesh, equations_parabolic, dg,1235cache)1236apply_jacobian_parabolic!(gradients_y, mesh, equations_parabolic, dg,1237cache)12381239return nothing1240end12411242# Needed to *not* flip the sign of the inverse Jacobian.1243# This is because the parabolic fluxes are assumed to be of the form1244# `du/dt + df/dx = dg/dx + source(x,t)`,1245# where f(u) is the inviscid flux and g(u) is the parabolic flux.1246function apply_jacobian_parabolic!(du::AbstractArray, mesh::TreeMesh{2},1247equations_parabolic::AbstractEquationsParabolic,1248dg::DG, cache)1249@unpack inverse_jacobian = cache.elements12501251@threaded for element in eachelement(dg, cache)1252factor = inverse_jacobian[element]12531254for j in eachnode(dg), i in eachnode(dg)1255for v in eachvariable(equations_parabolic)1256du[v, i, j, element] *= factor1257end1258end1259end12601261return nothing1262end12631264# Need dimension specific version to avoid error at dispatching1265function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic::Nothing,1266equations_parabolic::AbstractEquations{2}, dg::DG,1267cache)1268return nothing1269end12701271function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,1272equations_parabolic::AbstractEquations{2}, dg::DG,1273cache)1274@unpack node_coordinates = cache.elements12751276@threaded for element in eachelement(dg, cache)1277for j in eachnode(dg), i in eachnode(dg)1278u_local = get_node_vars(u, equations_parabolic, dg, i, j, element)1279gradients_x_local = get_node_vars(gradients[1], equations_parabolic, dg,1280i, j, element)1281gradients_y_local = get_node_vars(gradients[2], equations_parabolic, dg,1282i, j, element)1283gradients_local = (gradients_x_local, gradients_y_local)1284x_local = get_node_coords(node_coordinates, equations_parabolic, dg,1285i, j, element)1286du_local = source_terms_parabolic(u_local, gradients_local, x_local, t,1287equations_parabolic)1288add_to_node_vars!(du, du_local, equations_parabolic, dg, i, j, element)1289end1290end12911292return nothing1293end1294end # @muladd129512961297