Path: blob/main/src/callbacks_step/analysis_dgmulti.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 calc_error_norms(func, u, t, analyzer,8mesh::DGMultiMesh{NDIMS}, equations, initial_condition,9dg::DGMulti{NDIMS}, cache, cache_analysis) where {NDIMS}10rd = dg.basis11md = mesh.md12(; u_values) = cache.solution_container1314# interpolate u to quadrature points15apply_to_each_field(mul_by!(rd.Vq), u_values, u)1617component_l2_errors = zero(eltype(u_values))18component_linf_errors = zero(eltype(u_values))19for i in each_quad_node_global(mesh, dg, cache)20u_exact = initial_condition(SVector(getindex.(md.xyzq, i)), t, equations)21error_at_node = func(u_values[i], equations) - func(u_exact, equations)22component_l2_errors += md.wJq[i] * error_at_node .^ 223component_linf_errors = max.(component_linf_errors, abs.(error_at_node))24end25total_volume = sum(md.wJq)26return sqrt.(component_l2_errors ./ total_volume), component_linf_errors27end2829function integrate(func::Func, u, mesh::DGMultiMesh,30equations, dg::DGMulti, cache; normalize = true) where {Func}31rd = dg.basis32md = mesh.md33(; u_values) = cache.solution_container3435# interpolate u to quadrature points36apply_to_each_field(mul_by!(rd.Vq), u_values, u)3738integral = sum(md.wJq .* func.(u_values, equations))39if normalize == true40integral /= sum(md.wJq)41end42return integral43end4445function analyze(::typeof(entropy_timederivative), du, u, t,46mesh::DGMultiMesh, equations, dg::DGMulti, cache)47rd = dg.basis48md = mesh.md49(; u_values) = cache.solution_container5051# interpolate u, du to quadrature points52du_values = similar(u_values) # Todo: DGMulti. Can we move this to the analysis cache somehow?53apply_to_each_field(mul_by!(rd.Vq), du_values, du)54apply_to_each_field(mul_by!(rd.Vq), u_values, u)5556# compute ∫v(u) * du/dt = ∫dS/dt. We can directly compute v(u) instead of computing the entropy57# projection here, since the RHS will be projected to polynomials of degree N and testing with58# the L2 projection of v(u) would be equivalent to testing with v(u) due to the moment-preserving59# property of the L2 projection.60dS_dt = zero(eltype(first(du)))61for i in Base.OneTo(length(md.wJq))62dS_dt += dot(cons2entropy(u_values[i], equations), du_values[i]) * md.wJq[i]63end64return dS_dt65end6667# This function is used in `analyze(::Val{:l2_divb},...)` and `analyze(::Val{:linf_divb},...)`68function compute_local_divergence!(local_divergence, element, vector_field,69mesh, dg::DGMulti, cache)70@unpack md = mesh71rd = dg.basis72uEltype = eltype(first(vector_field))7374fill!(local_divergence, zero(uEltype))7576# computes dU_i/dx_i = ∑_j dxhat_j/dx_i * dU_i / dxhat_j77# dU_i/dx_i is then accumulated into local_divergence.78# TODO: DGMulti. Extend to curved elements.79for i in eachdim(mesh)80for j in eachdim(mesh)81geometric_scaling = md.rstxyzJ[i, j][1, element]82jth_ref_derivative_matrix = rd.Drst[j]83mul!(local_divergence, jth_ref_derivative_matrix, vector_field[i],84geometric_scaling, one(uEltype))85end86end87end8889get_component(u::StructArray, i::Int) = StructArrays.component(u, i)90get_component(u::AbstractArray{<:SVector}, i::Int) = getindex.(u, i)9192function analyze(::Val{:l2_divb}, du, u, t,93mesh::DGMultiMesh, equations::IdealGlmMhdEquations2D,94dg::DGMulti, cache)95@unpack md = mesh96rd = dg.basis97B1 = get_component(u, 6)98B2 = get_component(u, 7)99B = (B1, B2)100101uEltype = eltype(B1)102l2norm_divB = zero(uEltype)103local_divB = zeros(uEltype, size(B1, 1))104for e in eachelement(mesh, dg, cache)105compute_local_divergence!(local_divB, e, view.(B, :, e), mesh, dg, cache)106107# TODO: DGMulti. Extend to curved elements.108# compute L2 norm squared via J[1, e] * u' * M * u109local_l2norm_divB = md.J[1, e] * dot(local_divB, rd.M, local_divB)110l2norm_divB += local_l2norm_divB111end112113return sqrt(l2norm_divB)114end115116function analyze(::Val{:linf_divb}, du, u, t,117mesh::DGMultiMesh, equations::IdealGlmMhdEquations2D,118dg::DGMulti, cache)119B1 = get_component(u, 6)120B2 = get_component(u, 7)121B = (B1, B2)122123uEltype = eltype(B1)124linf_divB = zero(uEltype)125local_divB = zeros(uEltype, size(B1, 1))126@batch reduction=(max, linf_divb) for e in eachelement(mesh, dg, cache)127compute_local_divergence!(local_divB, e, view.(B, :, e), mesh, dg, cache)128129# compute maximum norm130linf_divB = max(linf_divB, maximum(abs, local_divB))131end132133return linf_divB134end135136# Calculate ∫_e (∂S/∂u ⋅ ∂u/∂t) dΩ_e where the result on element 'e' is kept in reference space137# Note that ∂S/∂u = w(u) with entropy variables w.138# This assumes that both du and u are already interpolated to the quadrature points139function entropy_change_reference_element(du_values_local, u_values_local,140mesh::DGMultiMesh, equations,141dg::DGMulti, cache)142rd = dg.basis143@unpack Nq, wq = rd144145# Compute entropy change for this element146dS_dt_elem = zero(eltype(first(du_values_local)))147for i in Base.OneTo(Nq) # Loop over quadrature points in the element148dS_dt_elem += dot(cons2entropy(u_values_local[i], equations),149du_values_local[i]) * wq[i]150end151152return dS_dt_elem153end154155# calculate surface integral of func(u, normal_direction, equations) on the reference element.156# For DGMulti, we loop over all faces of the element and integrate using face quadrature weights.157# Restricted to `Polynomial` approximation type which requires interpolation to face quadrature nodes158function surface_integral_reference_element(func::Func, u, element,159mesh::DGMultiMesh, equations,160dg::DGMulti,161cache, args...) where {Func}162rd = dg.basis163@unpack Nfq, wf, Vf = rd164md = mesh.md165@unpack nxyzJ = md166167# Interpolate volume solution to face quadrature nodes for this element168@unpack u_face_local_threaded = cache169u_face_local = u_face_local_threaded[Threads.threadid()]170u_elem = view(u, :, element)171apply_to_each_field(mul_by!(Vf), u_face_local, u_elem)172173surface_integral = zero(eltype(first(u)))174# Loop over all face nodes for this element175for i in 1:Nfq176# Get solution at this face node177u_node = u_face_local[i]178179# Get face normal; nxyzJ stores components as (nxJ, nyJ, nxJ)180normal_direction = SVector(getindex.(nxyzJ, i, element))181182# Multiply with face quadrature weight and accumulate183surface_integral += wf[i] * func(u_node, normal_direction, equations)184end185186return surface_integral187end188189function create_cache_analysis(analyzer, mesh::DGMultiMesh,190equations, dg::DGMulti, cache,191RealT, uEltype)192return (;)193end194195SolutionAnalyzer(rd::RefElemData) = rd196197nelements(mesh::DGMultiMesh, ::DGMulti, other_args...) = mesh.md.num_elements198function nelementsglobal(mesh::DGMultiMesh, solver::DGMulti, cache)199if mpi_isparallel()200error("`nelementsglobal` is not implemented for `DGMultiMesh` when used in parallel with MPI")201else202return nelements(mesh, solver)203end204end205function ndofsglobal(mesh::DGMultiMesh, solver::DGMulti, cache)206if mpi_isparallel()207error("`ndofsglobal` is not implemented for `DGMultiMesh` when used in parallel with MPI")208else209return ndofs(mesh, solver, cache)210end211end212end # @muladd213214215