Path: blob/main/src/solvers/fdsbp_tree/fdsbp_3d.jl
5591 views
# !!! warning "Experimental implementation (upwind SBP)"1# This is an experimental feature and may change in future releases.23# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).4# Since these FMAs can increase the performance of many numerical algorithms,5# we need to opt-in explicitly.6# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.7@muladd begin8#! format: noindent910# 3D caches11function create_cache(mesh::TreeMesh{3}, equations,12volume_integral::VolumeIntegralStrongForm,13dg, cache_containers, uEltype)14prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}(undef,15ntuple(_ -> nnodes(dg),16ndims(mesh))...)17f_threaded = [similar(prototype) for _ in 1:Threads.maxthreadid()]1819return (; f_threaded)20end2122function create_cache(mesh::TreeMesh{3}, equations,23volume_integral::VolumeIntegralUpwind,24dg, cache_containers, uEltype)25u_node = SVector{nvariables(equations), uEltype}(ntuple(_ -> zero(uEltype),26Val{nvariables(equations)}()))27f = StructArray([(u_node, u_node)])28f_minus_plus_threaded = [similar(f, ntuple(_ -> nnodes(dg), ndims(mesh))...)29for _ in 1:Threads.maxthreadid()]3031f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[1])32f_minus_threaded = [f_minus]33f_plus_threaded = [f_plus]34for i in 2:Threads.maxthreadid()35f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[i])36push!(f_minus_threaded, f_minus)37push!(f_plus_threaded, f_plus)38end3940return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded)41end4243# 3D volume integral contributions for `VolumeIntegralStrongForm`44function calc_volume_integral!(backend::Nothing, du, u,45mesh::TreeMesh{3},46have_nonconservative_terms::False, equations,47volume_integral::VolumeIntegralStrongForm,48dg::FDSBP, cache)49D = dg.basis # SBP derivative operator50@unpack f_threaded = cache5152# SBP operators from SummationByPartsOperators.jl implement the basic interface53# of matrix-vector multiplication. Thus, we pass an "array of structures",54# packing all variables per node in an `SVector`.55if nvariables(equations) == 156# `reinterpret(reshape, ...)` removes the leading dimension only if more57# than one variable is used.58u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u),59nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache))60du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)},61du),62nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache))63else64u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u)65du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)},66du)67end6869# Use the tensor product structure to compute the discrete derivatives of70# the fluxes line-by-line and add them to `du` for each element.71@threaded for element in eachelement(dg, cache)72f_element = f_threaded[Threads.threadid()]73u_element = view(u_vectors, :, :, :, element)7475# x direction76@. f_element = flux(u_element, 1, equations)77for j in eachnode(dg), k in eachnode(dg)78mul!(view(du_vectors, :, j, k, element), D, view(f_element, :, j, k),79one(eltype(du)), one(eltype(du)))80end8182# y direction83@. f_element = flux(u_element, 2, equations)84for i in eachnode(dg), k in eachnode(dg)85mul!(view(du_vectors, i, :, k, element), D, view(f_element, i, :, k),86one(eltype(du)), one(eltype(du)))87end8889# z direction90@. f_element = flux(u_element, 3, equations)91for i in eachnode(dg), j in eachnode(dg)92mul!(view(du_vectors, i, j, :, element), D, view(f_element, i, j, :),93one(eltype(du)), one(eltype(du)))94end95end9697return nothing98end99100# 3D volume integral contributions for `VolumeIntegralUpwind`.101# Note that the plus / minus notation of the operators does not refer to the102# upwind / downwind directions of the fluxes.103# Instead, the plus / minus refers to the direction of the biasing within104# the finite difference stencils. Thus, the D^- operator acts on the positive105# part of the flux splitting f^+ and the D^+ operator acts on the negative part106# of the flux splitting f^-.107function calc_volume_integral!(backend::Nothing, du, u,108mesh::TreeMesh{3},109have_nonconservative_terms::False, equations,110volume_integral::VolumeIntegralUpwind,111dg::FDSBP, cache)112# Assume that113# dg.basis isa SummationByPartsOperators.UpwindOperators114D_minus = dg.basis.minus # Upwind SBP D^- derivative operator115D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator116@unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache117@unpack splitting = volume_integral118119# SBP operators from SummationByPartsOperators.jl implement the basic interface120# of matrix-vector multiplication. Thus, we pass an "array of structures",121# packing all variables per node in an `SVector`.122if nvariables(equations) == 1123# `reinterpret(reshape, ...)` removes the leading dimension only if more124# than one variable is used.125u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u),126nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache))127du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)},128du),129nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache))130else131u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u)132du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)},133du)134end135136# Use the tensor product structure to compute the discrete derivatives of137# the fluxes line-by-line and add them to `du` for each element.138@threaded for element in eachelement(dg, cache)139# f_minus_plus_element wraps the storage provided by f_minus_element and140# f_plus_element such that we can use a single plain broadcasting below.141# f_minus_element and f_plus_element are updated in broadcasting calls142# of the form `@. f_minus_plus_element = ...`.143f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()]144f_minus_element = f_minus_threaded[Threads.threadid()]145f_plus_element = f_plus_threaded[Threads.threadid()]146u_element = view(u_vectors, :, :, :, element)147148# x direction149@. f_minus_plus_element = splitting(u_element, 1, equations)150for j in eachnode(dg), k in eachnode(dg)151mul!(view(du_vectors, :, j, k, element), D_minus,152view(f_plus_element, :, j, k),153one(eltype(du)), one(eltype(du)))154mul!(view(du_vectors, :, j, k, element), D_plus,155view(f_minus_element, :, j, k),156one(eltype(du)), one(eltype(du)))157end158159# y direction160@. f_minus_plus_element = splitting(u_element, 2, equations)161for i in eachnode(dg), k in eachnode(dg)162mul!(view(du_vectors, i, :, k, element), D_minus,163view(f_plus_element, i, :, k),164one(eltype(du)), one(eltype(du)))165mul!(view(du_vectors, i, :, k, element), D_plus,166view(f_minus_element, i, :, k),167one(eltype(du)), one(eltype(du)))168end169170# z direction171@. f_minus_plus_element = splitting(u_element, 3, equations)172for i in eachnode(dg), j in eachnode(dg)173mul!(view(du_vectors, i, j, :, element), D_minus,174view(f_plus_element, i, j, :),175one(eltype(du)), one(eltype(du)))176mul!(view(du_vectors, i, j, :, element), D_plus,177view(f_minus_element, i, j, :),178one(eltype(du)), one(eltype(du)))179end180end181182return nothing183end184185function calc_surface_integral!(backend::Nothing, du, u, mesh::TreeMesh{3},186equations, surface_integral::SurfaceIntegralStrongForm,187dg::DG, cache)188inv_weight_left = inv(left_boundary_weight(dg.basis))189inv_weight_right = inv(right_boundary_weight(dg.basis))190@unpack surface_flux_values = cache.elements191192@threaded for element in eachelement(dg, cache)193for m in eachnode(dg), l in eachnode(dg)194# surface at -x195u_node = get_node_vars(u, equations, dg, 1, l, m, element)196f_node = flux(u_node, 1, equations)197f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 1, element)198multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),199equations, dg, 1, l, m, element)200201# surface at +x202u_node = get_node_vars(u, equations, dg, nnodes(dg), l, m, element)203f_node = flux(u_node, 1, equations)204f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 2, element)205multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),206equations, dg, nnodes(dg), l, m, element)207208# surface at -y209u_node = get_node_vars(u, equations, dg, l, 1, m, element)210f_node = flux(u_node, 2, equations)211f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 3, element)212multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),213equations, dg, l, 1, m, element)214215# surface at +y216u_node = get_node_vars(u, equations, dg, l, nnodes(dg), m, element)217f_node = flux(u_node, 2, equations)218f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 4, element)219multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),220equations, dg, l, nnodes(dg), m, element)221222# surface at -z223u_node = get_node_vars(u, equations, dg, l, m, 1, element)224f_node = flux(u_node, 3, equations)225f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 5, element)226multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),227equations, dg, l, m, 1, element)228229# surface at +z230u_node = get_node_vars(u, equations, dg, l, m, nnodes(dg), element)231f_node = flux(u_node, 3, equations)232f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 6, element)233multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),234equations, dg, l, m, nnodes(dg), element)235end236end237238return nothing239end240241# Periodic FDSBP operators need to use a single element without boundaries242function calc_surface_integral!(backend::Nothing, du, u, mesh::TreeMesh3D,243equations, surface_integral::SurfaceIntegralStrongForm,244dg::PeriodicFDSBP, cache)245@assert nelements(dg, cache) == 1246return nothing247end248249# Specialized interface flux computation because the upwind solver does250# not require a standard numerical flux (Riemann solver). The flux splitting251# already separates the solution information into right-traveling and252# left-traveling information. So we only need to compute the appropriate253# flux information at each side of an interface.254function calc_interface_flux!(backend::Nothing, surface_flux_values,255mesh::TreeMesh{3},256have_nonconservative_terms::False, equations,257surface_integral::SurfaceIntegralUpwind,258dg::FDSBP, cache)259@unpack splitting = surface_integral260@unpack u, neighbor_ids, orientations = cache.interfaces261262@threaded for interface in eachinterface(dg, cache)263# Get neighboring elements264left_id = neighbor_ids[1, interface]265right_id = neighbor_ids[2, interface]266267# Determine interface direction with respect to elements:268# orientation = 1: left -> 2, right -> 1269# orientation = 2: left -> 4, right -> 3270# orientation = 3: left -> 6, right -> 5271left_direction = 2 * orientations[interface]272right_direction = 2 * orientations[interface] - 1273274for j in eachnode(dg), i in eachnode(dg)275# Pull the left and right solution data276u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, interface)277278# Compute the upwind coupling terms where right-traveling279# information comes from the left and left-traveling information280# comes from the right281flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface],282equations)283flux_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface],284equations)285286# Save the upwind coupling into the appropriate side of the elements287for v in eachvariable(equations)288surface_flux_values[v, i, j, left_direction, left_id] = flux_minus_rr[v]289surface_flux_values[v, i, j, right_direction, right_id] = flux_plus_ll[v]290end291end292end293294return nothing295end296297# Implementation of fully upwind SATs. The surface flux values are pre-computed298# in the specialized `calc_interface_flux` routine. These SATs are still of299# a strong form penalty type, except that the interior flux at a particular300# side of the element are computed in the upwind direction.301function calc_surface_integral!(backend::Nothing, du, u, mesh::TreeMesh{3},302equations, surface_integral::SurfaceIntegralUpwind,303dg::FDSBP, cache)304inv_weight_left = inv(left_boundary_weight(dg.basis))305inv_weight_right = inv(right_boundary_weight(dg.basis))306@unpack surface_flux_values = cache.elements307@unpack splitting = surface_integral308309@threaded for element in eachelement(dg, cache)310for m in eachnode(dg), l in eachnode(dg)311# surface at -x312u_node = get_node_vars(u, equations, dg, 1, l, m, element)313f_node = splitting(u_node, Val{:plus}(), 1, equations)314f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 1, element)315multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),316equations, dg, 1, l, m, element)317318# surface at +x319u_node = get_node_vars(u, equations, dg, nnodes(dg), l, m, element)320f_node = splitting(u_node, Val{:minus}(), 1, equations)321f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 2, element)322multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),323equations, dg, nnodes(dg), l, m, element)324325# surface at -y326u_node = get_node_vars(u, equations, dg, l, 1, m, element)327f_node = splitting(u_node, Val{:plus}(), 2, equations)328f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 3, element)329multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),330equations, dg, l, 1, m, element)331332# surface at +y333u_node = get_node_vars(u, equations, dg, l, nnodes(dg), m, element)334f_node = splitting(u_node, Val{:minus}(), 2, equations)335f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 4, element)336multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),337equations, dg, l, nnodes(dg), m, element)338339# surface at -z340u_node = get_node_vars(u, equations, dg, l, m, 1, element)341f_node = splitting(u_node, Val{:plus}(), 3, equations)342f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 5, element)343multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),344equations, dg, l, m, 1, element)345346# surface at +z347u_node = get_node_vars(u, equations, dg, l, m, nnodes(dg), element)348f_node = splitting(u_node, Val{:minus}(), 3, equations)349f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 6, element)350multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),351equations, dg, l, m, nnodes(dg), element)352end353end354355return nothing356end357358# Periodic FDSBP operators need to use a single element without boundaries359function calc_surface_integral!(backend::Nothing, du, u, mesh::TreeMesh3D,360equations, surface_integral::SurfaceIntegralUpwind,361dg::PeriodicFDSBP, cache)362@assert nelements(dg, cache) == 1363return nothing364end365366# AnalysisCallback367368function integrate_via_indices(func::Func, u,369mesh::TreeMesh{3}, equations,370dg::FDSBP, cache, args...; normalize = true) where {Func}371# TODO: FD. This is rather inefficient right now and allocates...372M = SummationByPartsOperators.mass_matrix(dg.basis)373if M isa UniformScaling374M = M(nnodes(dg))375end376weights = diag(M)377378# Initialize integral with zeros of the right shape379integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...))380381# Use quadrature to numerically integrate over entire domain382@batch reduction=(+, integral) for element in eachelement(dg, cache)383volume_jacobian_ = volume_jacobian(element, mesh, cache)384for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)385integral += volume_jacobian_ * weights[i] * weights[j] * weights[k] *386func(u, i, j, k, element, equations, dg, args...)387end388end389390# Normalize with total volume391if normalize392integral = integral / total_volume(mesh)393end394395return integral396end397398function calc_error_norms(func, u, t, analyzer,399mesh::TreeMesh{3}, equations, initial_condition,400dg::FDSBP, cache, cache_analysis)401# TODO: FD. This is rather inefficient right now and allocates...402M = SummationByPartsOperators.mass_matrix(dg.basis)403if M isa UniformScaling404M = M(nnodes(dg))405end406weights = diag(M)407@unpack node_coordinates = cache.elements408409# Set up data structures410l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations))411linf_error = copy(l2_error)412413# Iterate over all elements for error calculations414for element in eachelement(dg, cache)415# Calculate errors at each node416volume_jacobian_ = volume_jacobian(element, mesh, cache)417418for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer)419u_exact = initial_condition(get_node_coords(node_coordinates, equations, dg,420i, j, k, element), t, equations)421diff = func(u_exact, equations) -422func(get_node_vars(u, equations, dg, i, j, k, element), equations)423l2_error += diff .^ 2 *424(weights[i] * weights[j] * weights[k] * volume_jacobian_)425linf_error = @. max(linf_error, abs(diff))426end427end428429# For L2 error, divide by total volume430total_volume_ = total_volume(mesh)431l2_error = @. sqrt(l2_error / total_volume_)432433return l2_error, linf_error434end435end # @muladd436437438