Path: blob/main/src/callbacks_step/analysis_dg2d.jl
5586 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: noindent67function create_cache_analysis(analyzer, mesh::TreeMesh{2},8equations, dg::DG, cache,9RealT, uEltype)1011# pre-allocate buffers12# We use `StrideArray`s here since these buffers are used in performance-critical13# places and the additional information passed to the compiler makes them faster14# than native `Array`s.15u_local = StrideArray(undef, uEltype,16StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),17StaticInt(nnodes(analyzer)))18u_tmp1 = StrideArray(undef, uEltype,19StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),20StaticInt(nnodes(dg)))21x_local = StrideArray(undef, RealT,22StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),23StaticInt(nnodes(analyzer)))24x_tmp1 = StrideArray(undef, RealT,25StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),26StaticInt(nnodes(dg)))2728return (; u_local, u_tmp1, x_local, x_tmp1)29end3031# Specialized cache for P4estMesh to allow for different ambient dimension from mesh dimension32function create_cache_analysis(analyzer,33mesh::Union{P4estMesh{2, NDIMS_AMBIENT},34P4estMeshView{2, NDIMS_AMBIENT}},35equations, dg::DG, cache,36RealT, uEltype) where {NDIMS_AMBIENT}3738# pre-allocate buffers39# We use `StrideArray`s here since these buffers are used in performance-critical40# places and the additional information passed to the compiler makes them faster41# than native `Array`s.42u_local = StrideArray(undef, uEltype,43StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),44StaticInt(nnodes(analyzer)))45u_tmp1 = StrideArray(undef, uEltype,46StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),47StaticInt(nnodes(dg)))48x_local = StrideArray(undef, RealT,49StaticInt(NDIMS_AMBIENT), StaticInt(nnodes(analyzer)),50StaticInt(nnodes(analyzer)))51x_tmp1 = StrideArray(undef, RealT,52StaticInt(NDIMS_AMBIENT), StaticInt(nnodes(analyzer)),53StaticInt(nnodes(dg)))54jacobian_local = StrideArray(undef, RealT,55StaticInt(nnodes(analyzer)),56StaticInt(nnodes(analyzer)))57jacobian_tmp1 = StrideArray(undef, RealT,58StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))5960return (; u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1)61end6263function create_cache_analysis(analyzer,64mesh::Union{StructuredMesh{2}, StructuredMeshView{2},65UnstructuredMesh2D, T8codeMesh{2}},66equations, dg::DG, cache,67RealT, uEltype)6869# pre-allocate buffers70# We use `StrideArray`s here since these buffers are used in performance-critical71# places and the additional information passed to the compiler makes them faster72# than native `Array`s.73u_local = StrideArray(undef, uEltype,74StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),75StaticInt(nnodes(analyzer)))76u_tmp1 = StrideArray(undef, uEltype,77StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),78StaticInt(nnodes(dg)))79x_local = StrideArray(undef, RealT,80StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),81StaticInt(nnodes(analyzer)))82x_tmp1 = StrideArray(undef, RealT,83StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),84StaticInt(nnodes(dg)))85jacobian_local = StrideArray(undef, RealT,86StaticInt(nnodes(analyzer)),87StaticInt(nnodes(analyzer)))88jacobian_tmp1 = StrideArray(undef, RealT,89StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))9091return (; u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1)92end9394function calc_error_norms(func, u, t, analyzer,95mesh::TreeMesh{2}, equations, initial_condition,96dg::DGSEM, cache, cache_analysis)97@unpack vandermonde, weights = analyzer98@unpack node_coordinates = cache.elements99@unpack u_local, u_tmp1, x_local, x_tmp1 = cache_analysis100101# Set up data structures102l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations))103linf_error = copy(l2_error)104105# Iterate over all elements for error calculations106# Accumulate L2 error on the element first so that the order of summation is the107# same as in the parallel case to ensure exact equality. This facilitates easier parallel108# development and debugging (see109# https://github.com/trixi-framework/Trixi.jl/pull/850#pullrequestreview-757463943 for details).110for element in eachelement(dg, cache)111# Set up data structures for local element L2 error112l2_error_local = zero(l2_error)113114# Interpolate solution and node locations to analysis nodes115multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1)116multiply_dimensionwise!(x_local, vandermonde,117view(node_coordinates, :, :, :, element), x_tmp1)118119# Calculate errors at each analysis node120volume_jacobian_ = volume_jacobian(element, mesh, cache)121122for j in eachnode(analyzer), i in eachnode(analyzer)123u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j),124t, equations)125diff = func(u_exact, equations) -126func(get_node_vars(u_local, equations, dg, i, j), equations)127l2_error_local += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian_)128linf_error = @. max(linf_error, abs(diff))129end130l2_error += l2_error_local131end132133# For L2 error, divide by total volume134total_volume_ = total_volume(mesh)135l2_error = @. sqrt(l2_error / total_volume_)136137return l2_error, linf_error138end139140function calc_error_norms(func, u, t, analyzer,141mesh::Union{StructuredMesh{2}, StructuredMeshView{2},142UnstructuredMesh2D,143P4estMesh{2}, P4estMeshView{2},144T8codeMesh{2}},145equations,146initial_condition, dg::DGSEM, cache, cache_analysis)147@unpack vandermonde, weights = analyzer148@unpack u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1 = cache_analysis149@unpack node_coordinates, inverse_jacobian = cache.elements150151# Calculate error norms on the CPU, to ensure the order of summation is the same.152if trixi_backend(u) !== nothing153node_coordinates = Array(node_coordinates)154inverse_jacobian = Array(inverse_jacobian)155u = Array(u)156end157158# Set up data structures159l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations))160linf_error = copy(l2_error)161total_volume = zero(real(mesh))162163# Iterate over all elements for error calculations164for element in eachelement(dg, cache)165# Interpolate solution and node locations to analysis nodes166multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1)167multiply_dimensionwise!(x_local, vandermonde,168view(node_coordinates, :, :, :, element), x_tmp1)169multiply_scalar_dimensionwise!(jacobian_local, vandermonde,170inv.(view(inverse_jacobian, :, :, element)),171jacobian_tmp1)172173# Calculate errors at each analysis node174for j in eachnode(analyzer), i in eachnode(analyzer)175u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j),176t, equations)177diff = func(u_exact, equations) -178func(get_node_vars(u_local, equations, dg, i, j), equations)179# We take absolute value as we need the Jacobian here for the volume calculation180abs_jacobian_local_ij = abs(jacobian_local[i, j])181182l2_error += diff .^ 2 * (weights[i] * weights[j] * abs_jacobian_local_ij)183linf_error = @. max(linf_error, abs(diff))184total_volume += weights[i] * weights[j] * abs_jacobian_local_ij185end186end187188# For L2 error, divide by total volume189l2_error = @. sqrt(l2_error / total_volume)190191return l2_error, linf_error192end193194# Use quadrature to numerically integrate a single element.195# We do not multiply by the Jacobian to stay in reference space.196# This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing197# the time derivative of entropy, see `entropy_change_reference_element`.198function integrate_reference_element(func::Func, u, element,199::Type{<:AbstractMesh{2}}, equations, dg::DGSEM,200cache,201args...) where {Func}202@unpack weights = dg.basis203204# Initialize integral with zeros of the right shape205element_integral = zero(func(u, 1, 1, element, equations, dg, args...))206207for j in eachnode(dg), i in eachnode(dg)208element_integral += weights[i] * weights[j] *209func(u, i, j, element, equations, dg, args...)210end211212return element_integral213end214215# Calculate ∫_e (∂S/∂u ⋅ ∂u/∂t) dΩ_e where the result on element 'e' is kept in reference space216# Note that ∂S/∂u = w(u) with entropy variables w217function entropy_change_reference_element(du, u, element,218MeshT::Type{<:AbstractMesh{2}},219equations, dg::DGSEM, cache, args...)220return integrate_reference_element(u, element, MeshT, equations, dg, cache,221du) do u, i, j, element, equations, dg, du222u_node = get_node_vars(u, equations, dg, i, j, element)223du_node = get_node_vars(du, equations, dg, i, j, element)224225dot(cons2entropy(u_node, equations), du_node)226end227end228229# calculate surface integral of func(u, equations) * normal on the reference element.230function surface_integral_reference_element(func::Func, u, element,231::Type{<:TreeMesh{2}}, equations, dg::DGSEM,232cache, args...) where {Func}233@unpack weights = dg.basis234235u_tmp = get_node_vars(u, equations, dg, 1, 1, element)236surface_integral = zero(func(u_tmp, 1, equations))237for i in eachnode(dg)238# integrate along x direction, normal in y (2) direction239u_bottom = get_node_vars(u, equations, dg, i, 1, element)240u_top = get_node_vars(u, equations, dg, i, nnodes(dg), element)241242surface_integral += weights[i] *243(func(u_top, 2, equations) - func(u_bottom, 2, equations))244245# integrate along y direction, normal in x (1) direction246u_left = get_node_vars(u, equations, dg, 1, i, element)247u_right = get_node_vars(u, equations, dg, nnodes(dg), i, element)248249surface_integral += weights[i] *250(func(u_right, 1, equations) - func(u_left, 1, equations))251end252253return surface_integral254end255256# calculate surface integral of func(u, normal_direction, equations) * normal on the reference element.257# Note: `get_normal_direction` already returns an outward-pointing normal for all directions,258# thus no +- flips are needed here.259function surface_integral_reference_element(func::Func, u, element,260::Type{<:Union{StructuredMesh{2},261StructuredMeshView{2},262UnstructuredMesh2D,263P4estMesh{2},264T8codeMesh{2}}},265equations, dg::DGSEM,266cache, args...) where {Func}267@unpack contravariant_vectors = cache.elements268@unpack weights = dg.basis269270# Construct zero of right shape:271# Evaluate `func` at actual quadrature node and normal direction272u_tmp = get_node_vars(u, equations, dg, 1, 1, element)273normal_direction = get_normal_direction(1, contravariant_vectors,2741, 1, element)275surface_integral = zero(func(u_tmp, normal_direction, equations))276277# Direction 1: face at i = 1 (x_min)278for j in eachnode(dg)279u_node = get_node_vars(u, equations, dg, 1, j, element)280normal_direction = get_normal_direction(1, contravariant_vectors,2811, j, element)282surface_integral += weights[j] * func(u_node, normal_direction, equations)283end284285# Direction 2: face at i = nnodes(dg) (x_max)286for j in eachnode(dg)287u_node = get_node_vars(u, equations, dg, nnodes(dg), j, element)288normal_direction = get_normal_direction(2, contravariant_vectors,289nnodes(dg), j, element)290surface_integral += weights[j] * func(u_node, normal_direction, equations)291end292293# Direction 3: face at j = 1 (y_min)294for i in eachnode(dg)295u_node = get_node_vars(u, equations, dg, i, 1, element)296normal_direction = get_normal_direction(3, contravariant_vectors,297i, 1, element)298surface_integral += weights[i] * func(u_node, normal_direction, equations)299end300301# Direction 4: face at j = nnodes(dg) (y_max)302for i in eachnode(dg)303u_node = get_node_vars(u, equations, dg, i, nnodes(dg), element)304normal_direction = get_normal_direction(4, contravariant_vectors,305i, nnodes(dg), element)306surface_integral += weights[i] * func(u_node, normal_direction, equations)307end308309return surface_integral310end311312function integrate_via_indices(func::Func, u,313mesh::TreeMesh{2}, equations, dg::DGSEM, cache,314args...; normalize = true) where {Func}315@unpack weights = dg.basis316317# Initialize integral with zeros of the right shape318integral = zero(func(u, 1, 1, 1, equations, dg, args...))319320# Use quadrature to numerically integrate over entire domain321@batch reduction=(+, integral) for element in eachelement(dg, cache)322volume_jacobian_ = volume_jacobian(element, mesh, cache)323for j in eachnode(dg), i in eachnode(dg)324integral += volume_jacobian_ * weights[i] * weights[j] *325func(u, i, j, element, equations, dg, args...)326end327end328329# Normalize with total volume330if normalize331integral = integral / total_volume(mesh)332end333334return integral335end336337function integrate_via_indices(func::Func, u,338mesh::Union{StructuredMesh{2}, StructuredMeshView{2},339UnstructuredMesh2D,340P4estMesh{2}, P4estMeshView{2},341T8codeMesh{2}},342equations, dg::DGSEM, cache,343args...; normalize = true) where {Func}344return integrate_via_indices(func, trixi_backend(u), u, mesh, equations, dg, cache,345args...; normalize = normalize)346end347348function integrate_via_indices(func::Func, ::Nothing, u,349mesh::Union{StructuredMesh{2}, StructuredMeshView{2},350UnstructuredMesh2D,351P4estMesh{2}, P4estMeshView{2},352T8codeMesh{2}},353equations, dg::DGSEM, cache,354args...; normalize = true) where {Func}355@unpack weights = dg.basis356@unpack inverse_jacobian = cache.elements357358# Initialize integral with zeros of the right shape359integral = zero(func(u, 1, 1, 1, equations, dg, args...))360total_volume = zero(real(mesh))361362# Use quadrature to numerically integrate over entire domain363@batch reduction=((+, integral), (+, total_volume)) for element in eachelement(dg,364cache)365for j in eachnode(dg), i in eachnode(dg)366volume_jacobian = abs(inv(inverse_jacobian[i, j, element]))367integral += volume_jacobian * weights[i] * weights[j] *368func(u, i, j, element, equations, dg, args...)369total_volume += volume_jacobian * weights[i] * weights[j]370end371end372373# Normalize with total volume374if normalize375integral = integral / total_volume376end377378return integral379end380381function integrate_via_indices(func::Func, backend::Backend, u,382mesh::Union{StructuredMesh{2}, StructuredMeshView{2},383UnstructuredMesh2D,384P4estMesh{2}, P4estMeshView{2},385T8codeMesh{2}},386equations, dg::DGSEM, cache,387args...; normalize = true) where {Func}388@unpack weights = dg.basis389@unpack inverse_jacobian = cache.elements390391function local_plus((integral, total_volume), (integral_element, volume_element))392return (integral + integral_element, total_volume + volume_element)393end394395# `func(u, 1,1,1, equations, dg, args...)` might access GPU memory396# so we have to rely on the compiler to correctly infer the type of the integral here.397# TODO: Technically we need device_promote_op here that "infers" the function within the context of the GPU.398integral0 = zero(Base.promote_op(func, typeof(u), Int, Int, Int, typeof(equations),399typeof(dg), map(typeof, args)...))400init = neutral = (integral0, zero(real(mesh)))401402# Use quadrature to numerically integrate over entire domain403num_elements = nelements(dg, cache)404integral, total_volume = AcceleratedKernels.mapreduce(local_plus, 1:num_elements,405backend; init,406neutral) do element407# Initialize integral with zeros of the right shapeu408local_integral, local_total_volume = neutral409410for j in eachnode(dg), i in eachnode(dg)411volume_jacobian = abs(inv(inverse_jacobian[i, j, element]))412local_integral += volume_jacobian * weights[i] * weights[j] *413func(u, i, j, element, equations, dg, args...)414local_total_volume += volume_jacobian * weights[i] * weights[j]415end416417return (local_integral, local_total_volume)418end419420# Normalize with total volume421if normalize422integral = integral / total_volume423end424425return integral426end427428function integrate(func::Func, u,429mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2},430UnstructuredMesh2D, P4estMesh{2}, P4estMeshView{2},431T8codeMesh{2}},432equations, dg::Union{DGSEM, FDSBP}, cache;433normalize = true) where {Func}434integrate_via_indices(u, mesh, equations, dg, cache;435normalize = normalize) do u, i, j, element, equations, dg436u_local = get_node_vars(u, equations, dg, i, j, element)437return func(u_local, equations)438end439end440441function integrate(func::Func, u,442mesh::Union{TreeMesh{2}, P4estMesh{2}},443equations, equations_parabolic, dg::DGSEM,444cache, cache_parabolic; normalize = true) where {Func}445gradients_x, gradients_y = cache_parabolic.parabolic_container.gradients446integrate_via_indices(u, mesh, equations, dg, cache;447normalize = normalize) do u, i, j, element, equations, dg448u_local = get_node_vars(u, equations, dg, i, j, element)449gradients_1_local = get_node_vars(gradients_x, equations_parabolic, dg, i, j,450element)451gradients_2_local = get_node_vars(gradients_y, equations_parabolic, dg, i, j,452element)453return func(u_local, (gradients_1_local, gradients_2_local),454equations_parabolic)455end456end457458function analyze(::typeof(entropy_timederivative), du, u, t,459mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2},460UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}},461equations, dg::Union{DGSEM, FDSBP}, cache)462# Calculate463# Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ464integrate_via_indices(u, mesh, equations, dg, cache,465du) do u, i, j, element, equations, dg, du466u_node = get_node_vars(u, equations, dg, i, j, element)467du_node = get_node_vars(du, equations, dg, i, j, element)468return dot(cons2entropy(u_node, equations), du_node)469end470end471472function analyze(::Val{:l2_divb}, du, u, t,473mesh::TreeMesh{2},474equations, dg::DGSEM, cache)475integrate_via_indices(u, mesh, equations, dg, cache, cache,476dg.basis.derivative_matrix) do u, i, j, element, equations,477dg, cache, derivative_matrix478divb = zero(eltype(u))479for k in eachnode(dg)480u_kj = get_node_vars(u, equations, dg, k, j, element)481u_ik = get_node_vars(u, equations, dg, i, k, element)482483B1_kj, _, _ = magnetic_field(u_kj, equations)484_, B2_ik, _ = magnetic_field(u_ik, equations)485486divb += (derivative_matrix[i, k] * B1_kj +487derivative_matrix[j, k] * B2_ik)488end489divb *= cache.elements.inverse_jacobian[element]490return divb^2491end |> sqrt492end493494function analyze(::Val{:l2_divb}, du, u, t,495mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2},496T8codeMesh{2}},497equations, dg::DGSEM, cache)498@unpack contravariant_vectors, inverse_jacobian = cache.elements499integrate_via_indices(u, mesh, equations, dg, cache, cache,500dg.basis.derivative_matrix) do u, i, j, element, equations,501dg, cache, derivative_matrix502divb = zero(eltype(u))503# Get the contravariant vectors Ja^1 and Ja^2504Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, element)505Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, element)506# Compute the transformed divergence507for k in eachnode(dg)508u_kj = get_node_vars(u, equations, dg, k, j, element)509u_ik = get_node_vars(u, equations, dg, i, k, element)510511B1_kj, B2_kj, _ = magnetic_field(u_kj, equations)512B1_ik, B2_ik, _ = magnetic_field(u_ik, equations)513514divb += (derivative_matrix[i, k] *515(Ja11 * B1_kj + Ja12 * B2_kj) +516derivative_matrix[j, k] *517(Ja21 * B1_ik + Ja22 * B2_ik))518end519divb *= inverse_jacobian[i, j, element]520return divb^2521end |> sqrt522end523524function analyze(::Val{:linf_divb}, du, u, t,525mesh::TreeMesh{2},526equations, dg::DGSEM, cache)527@unpack derivative_matrix, weights = dg.basis528529# integrate over all elements to get the divergence-free condition errors530linf_divb = zero(eltype(u))531@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)532for j in eachnode(dg), i in eachnode(dg)533divb = zero(eltype(u))534for k in eachnode(dg)535u_kj = get_node_vars(u, equations, dg, k, j, element)536u_ik = get_node_vars(u, equations, dg, i, k, element)537538B1_kj, _, _ = magnetic_field(u_kj, equations)539_, B2_ik, _ = magnetic_field(u_ik, equations)540541divb += (derivative_matrix[i, k] * B1_kj +542derivative_matrix[j, k] * B2_ik)543end544divb *= cache.elements.inverse_jacobian[element]545linf_divb = max(linf_divb, abs(divb))546end547end548549return linf_divb550end551552function analyze(::Val{:linf_divb}, du, u, t,553mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2},554T8codeMesh{2}},555equations, dg::DGSEM, cache)556@unpack derivative_matrix, weights = dg.basis557@unpack contravariant_vectors, inverse_jacobian = cache.elements558559# integrate over all elements to get the divergence-free condition errors560linf_divb = zero(eltype(u))561@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)562for j in eachnode(dg), i in eachnode(dg)563divb = zero(eltype(u))564# Get the contravariant vectors Ja^1 and Ja^2565Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors,566i, j, element)567Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors,568i, j, element)569# Compute the transformed divergence570for k in eachnode(dg)571u_kj = get_node_vars(u, equations, dg, k, j, element)572u_ik = get_node_vars(u, equations, dg, i, k, element)573574B1_kj, B2_kj, _ = magnetic_field(u_kj, equations)575B1_ik, B2_ik, _ = magnetic_field(u_ik, equations)576577divb += (derivative_matrix[i, k] *578(Ja11 * B1_kj + Ja12 * B2_kj) +579derivative_matrix[j, k] *580(Ja21 * B1_ik + Ja22 * B2_ik))581end582divb *= inverse_jacobian[i, j, element]583linf_divb = max(linf_divb, abs(divb))584end585end586if mpi_isparallel()587# Base.max instead of max needed, see comment in src/auxiliary/math.jl588linf_divb = MPI.Allreduce!(Ref(linf_divb), Base.max, mpi_comm())[]589end590591return linf_divb592end593end # @muladd594595596