Path: blob/main/src/callbacks_step/analysis_dg2d.jl
2055 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 node_coordinates, inverse_jacobian = cache.elements149@unpack u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1 = cache_analysis150151# Set up data structures152l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations))153linf_error = copy(l2_error)154total_volume = zero(real(mesh))155156# Iterate over all elements for error calculations157for element in eachelement(dg, cache)158# Interpolate solution and node locations to analysis nodes159multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1)160multiply_dimensionwise!(x_local, vandermonde,161view(node_coordinates, :, :, :, element), x_tmp1)162multiply_scalar_dimensionwise!(jacobian_local, vandermonde,163inv.(view(inverse_jacobian, :, :, element)),164jacobian_tmp1)165166# Calculate errors at each analysis node167for j in eachnode(analyzer), i in eachnode(analyzer)168u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j),169t, equations)170diff = func(u_exact, equations) -171func(get_node_vars(u_local, equations, dg, i, j), equations)172# We take absolute value as we need the Jacobian here for the volume calculation173abs_jacobian_local_ij = abs(jacobian_local[i, j])174175l2_error += diff .^ 2 * (weights[i] * weights[j] * abs_jacobian_local_ij)176linf_error = @. max(linf_error, abs(diff))177total_volume += weights[i] * weights[j] * abs_jacobian_local_ij178end179end180181# For L2 error, divide by total volume182l2_error = @. sqrt(l2_error / total_volume)183184return l2_error, linf_error185end186187function integrate_via_indices(func::Func, u,188mesh::TreeMesh{2}, equations, dg::DGSEM, cache,189args...; normalize = true) where {Func}190@unpack weights = dg.basis191192# Initialize integral with zeros of the right shape193integral = zero(func(u, 1, 1, 1, equations, dg, args...))194195# Use quadrature to numerically integrate over entire domain196@batch reduction=(+, integral) for element in eachelement(dg, cache)197volume_jacobian_ = volume_jacobian(element, mesh, cache)198for j in eachnode(dg), i in eachnode(dg)199integral += volume_jacobian_ * weights[i] * weights[j] *200func(u, i, j, element, equations, dg, args...)201end202end203204# Normalize with total volume205if normalize206integral = integral / total_volume(mesh)207end208209return integral210end211212function integrate_via_indices(func::Func, u,213mesh::Union{StructuredMesh{2}, StructuredMeshView{2},214UnstructuredMesh2D, P4estMesh{2},215T8codeMesh{2}},216equations,217dg::DGSEM, cache, args...; normalize = true) where {Func}218@unpack weights = dg.basis219220# Initialize integral with zeros of the right shape221integral = zero(func(u, 1, 1, 1, equations, dg, args...))222total_volume = zero(real(mesh))223224# Use quadrature to numerically integrate over entire domain225@batch reduction=((+, integral), (+, total_volume)) for element in eachelement(dg,226cache)227for j in eachnode(dg), i in eachnode(dg)228volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element]))229integral += volume_jacobian * weights[i] * weights[j] *230func(u, i, j, element, equations, dg, args...)231total_volume += volume_jacobian * weights[i] * weights[j]232end233end234235# Normalize with total volume236if normalize237integral = integral / total_volume238end239240return integral241end242243function integrate(func::Func, u,244mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2},245UnstructuredMesh2D, P4estMesh{2}, P4estMeshView{2},246T8codeMesh{2}},247equations, dg::DG, cache; normalize = true) where {Func}248integrate_via_indices(u, mesh, equations, dg, cache;249normalize = normalize) do u, i, j, element, equations, dg250u_local = get_node_vars(u, equations, dg, i, j, element)251return func(u_local, equations)252end253end254255function integrate(func::Func, u,256mesh::Union{TreeMesh{2}, P4estMesh{2}},257equations, equations_parabolic,258dg::DGSEM,259cache, cache_parabolic; normalize = true) where {Func}260gradients_x, gradients_y = cache_parabolic.viscous_container.gradients261integrate_via_indices(u, mesh, equations, dg, cache;262normalize = normalize) do u, i, j, element, equations, dg263u_local = get_node_vars(u, equations, dg, i, j, element)264gradients_1_local = get_node_vars(gradients_x, equations_parabolic, dg, i, j,265element)266gradients_2_local = get_node_vars(gradients_y, equations_parabolic, dg, i, j,267element)268return func(u_local, (gradients_1_local, gradients_2_local),269equations_parabolic)270end271end272273function analyze(::typeof(entropy_timederivative), du, u, t,274mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2},275UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}},276equations, dg::DG, cache)277# Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ278integrate_via_indices(u, mesh, equations, dg, cache,279du) do u, i, j, element, equations, dg, du280u_node = get_node_vars(u, equations, dg, i, j, element)281du_node = get_node_vars(du, equations, dg, i, j, element)282dot(cons2entropy(u_node, equations), du_node)283end284end285286function analyze(::Val{:l2_divb}, du, u, t,287mesh::TreeMesh{2},288equations, dg::DGSEM, cache)289integrate_via_indices(u, mesh, equations, dg, cache, cache,290dg.basis.derivative_matrix) do u, i, j, element, equations,291dg, cache, derivative_matrix292divb = zero(eltype(u))293for k in eachnode(dg)294u_kj = get_node_vars(u, equations, dg, k, j, element)295u_ik = get_node_vars(u, equations, dg, i, k, element)296297B1_kj, _, _ = magnetic_field(u_kj, equations)298_, B2_ik, _ = magnetic_field(u_ik, equations)299300divb += (derivative_matrix[i, k] * B1_kj +301derivative_matrix[j, k] * B2_ik)302end303divb *= cache.elements.inverse_jacobian[element]304divb^2305end |> sqrt306end307308function analyze(::Val{:l2_divb}, du, u, t,309mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2},310T8codeMesh{2}},311equations, dg::DGSEM, cache)312@unpack contravariant_vectors = cache.elements313integrate_via_indices(u, mesh, equations, dg, cache, cache,314dg.basis.derivative_matrix) do u, i, j, element, equations,315dg, cache, derivative_matrix316divb = zero(eltype(u))317# Get the contravariant vectors Ja^1 and Ja^2318Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, element)319Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, element)320# Compute the transformed divergence321for k in eachnode(dg)322u_kj = get_node_vars(u, equations, dg, k, j, element)323u_ik = get_node_vars(u, equations, dg, i, k, element)324325B1_kj, B2_kj, _ = magnetic_field(u_kj, equations)326B1_ik, B2_ik, _ = magnetic_field(u_ik, equations)327328divb += (derivative_matrix[i, k] *329(Ja11 * B1_kj + Ja12 * B2_kj) +330derivative_matrix[j, k] *331(Ja21 * B1_ik + Ja22 * B2_ik))332end333divb *= cache.elements.inverse_jacobian[i, j, element]334divb^2335end |> sqrt336end337338function analyze(::Val{:linf_divb}, du, u, t,339mesh::TreeMesh{2},340equations, dg::DGSEM, cache)341@unpack derivative_matrix, weights = dg.basis342343# integrate over all elements to get the divergence-free condition errors344linf_divb = zero(eltype(u))345@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)346for j in eachnode(dg), i in eachnode(dg)347divb = zero(eltype(u))348for k in eachnode(dg)349u_kj = get_node_vars(u, equations, dg, k, j, element)350u_ik = get_node_vars(u, equations, dg, i, k, element)351352B1_kj, _, _ = magnetic_field(u_kj, equations)353_, B2_ik, _ = magnetic_field(u_ik, equations)354355divb += (derivative_matrix[i, k] * B1_kj +356derivative_matrix[j, k] * B2_ik)357end358divb *= cache.elements.inverse_jacobian[element]359linf_divb = max(linf_divb, abs(divb))360end361end362363return linf_divb364end365366function analyze(::Val{:linf_divb}, du, u, t,367mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2},368T8codeMesh{2}},369equations, dg::DGSEM, cache)370@unpack derivative_matrix, weights = dg.basis371@unpack contravariant_vectors = cache.elements372373# integrate over all elements to get the divergence-free condition errors374linf_divb = zero(eltype(u))375@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)376for j in eachnode(dg), i in eachnode(dg)377divb = zero(eltype(u))378# Get the contravariant vectors Ja^1 and Ja^2379Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors,380i, j, element)381Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors,382i, j, element)383# Compute the transformed divergence384for k in eachnode(dg)385u_kj = get_node_vars(u, equations, dg, k, j, element)386u_ik = get_node_vars(u, equations, dg, i, k, element)387388B1_kj, B2_kj, _ = magnetic_field(u_kj, equations)389B1_ik, B2_ik, _ = magnetic_field(u_ik, equations)390391divb += (derivative_matrix[i, k] *392(Ja11 * B1_kj + Ja12 * B2_kj) +393derivative_matrix[j, k] *394(Ja21 * B1_ik + Ja22 * B2_ik))395end396divb *= cache.elements.inverse_jacobian[i, j, element]397linf_divb = max(linf_divb, abs(divb))398end399end400if mpi_isparallel()401# Base.max instead of max needed, see comment in src/auxiliary/math.jl402linf_divb = MPI.Allreduce!(Ref(linf_divb), Base.max, mpi_comm())[]403end404405return linf_divb406end407end # @muladd408409410