Path: blob/main/src/callbacks_step/analysis_dg1d.jl
2802 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{1},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)))17x_local = StrideArray(undef, RealT,18StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)))1920return (; u_local, x_local)21end2223function create_cache_analysis(analyzer, mesh::StructuredMesh{1},24equations, dg::DG, cache,25RealT, uEltype)2627# pre-allocate buffers28# We use `StrideArray`s here since these buffers are used in performance-critical29# places and the additional information passed to the compiler makes them faster30# than native `Array`s.31u_local = StrideArray(undef, uEltype,32StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)))33x_local = StrideArray(undef, RealT,34StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)))35jacobian_local = StrideArray(undef, RealT,36StaticInt(nnodes(analyzer)))3738return (; u_local, x_local, jacobian_local)39end4041function calc_error_norms(func, u, t, analyzer,42mesh::TreeMesh{1}, equations, initial_condition,43dg::DGSEM, cache, cache_analysis)44@unpack vandermonde, weights = analyzer45@unpack node_coordinates = cache.elements46@unpack u_local, x_local = cache_analysis4748# Set up data structures49l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1), equations))50linf_error = copy(l2_error)5152# Iterate over all elements for error calculations53for element in eachelement(dg, cache)54# Interpolate solution and node locations to analysis nodes55multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, element))56multiply_dimensionwise!(x_local, vandermonde,57view(node_coordinates, :, :, element))5859# Calculate errors at each analysis node60volume_jacobian_ = volume_jacobian(element, mesh, cache)6162for i in eachnode(analyzer)63u_exact = initial_condition(get_node_coords(x_local, equations, dg, i), t,64equations)65diff = func(u_exact, equations) -66func(get_node_vars(u_local, equations, dg, i), equations)67l2_error += diff .^ 2 * (weights[i] * volume_jacobian_)68linf_error = @. max(linf_error, abs(diff))69end70end7172# For L2 error, divide by total volume73total_volume_ = total_volume(mesh)74l2_error = @. sqrt(l2_error / total_volume_)7576return l2_error, linf_error77end7879function calc_error_norms(func, u, t, analyzer,80mesh::StructuredMesh{1}, equations, initial_condition,81dg::DGSEM, cache, cache_analysis)82@unpack vandermonde, weights = analyzer83@unpack node_coordinates, inverse_jacobian = cache.elements84@unpack u_local, x_local, jacobian_local = cache_analysis8586# Set up data structures87l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1), equations))88linf_error = copy(l2_error)89total_volume = zero(real(mesh))9091# Iterate over all elements for error calculations92for element in eachelement(dg, cache)93# Interpolate solution and node locations to analysis nodes94multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, element))95multiply_dimensionwise!(x_local, vandermonde,96view(node_coordinates, :, :, element))97multiply_scalar_dimensionwise!(jacobian_local, vandermonde,98inv.(view(inverse_jacobian, :, element)))99100# Calculate errors at each analysis node101for i in eachnode(analyzer)102u_exact = initial_condition(get_node_coords(x_local, equations, dg, i), t,103equations)104diff = func(u_exact, equations) -105func(get_node_vars(u_local, equations, dg, i), equations)106# We take absolute value as we need the Jacobian here for the volume calculation107abs_jacobian_local_i = abs(jacobian_local[i])108109l2_error += diff .^ 2 * (weights[i] * abs_jacobian_local_i)110linf_error = @. max(linf_error, abs(diff))111total_volume += weights[i] * abs_jacobian_local_i112end113end114115# For L2 error, divide by total volume116l2_error = @. sqrt(l2_error / total_volume)117118return l2_error, linf_error119end120121# Use quadrature to numerically integrate a single element.122# We do not multiply by the Jacobian to stay in reference space.123# This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing124# the time derivative of entropy, see `entropy_change_reference_element`.125function integrate_reference_element(func::Func, u, element,126mesh::AbstractMesh{1}, equations, dg::DGSEM, cache,127args...) where {Func}128@unpack weights = dg.basis129130# Initialize integral with zeros of the right shape131element_integral = zero(func(u, 1, element, equations, dg, args...))132133for i in eachnode(dg)134element_integral += weights[i] *135func(u, i, element, equations, dg, args...)136end137138return element_integral139end140141# Calculate ∫_e (∂S/∂u ⋅ ∂u/∂t) dΩ_e where the result on element 'e' is kept in reference space142# Note that ∂S/∂u = w(u) with entropy variables w143function entropy_change_reference_element(du, u, element,144mesh::AbstractMesh{1},145equations, dg::DGSEM, cache, args...)146return integrate_reference_element(u, element, mesh, equations, dg, cache,147du) do u, i, element, equations, dg, du148u_node = get_node_vars(u, equations, dg, i, element)149du_node = get_node_vars(du, equations, dg, i, element)150151dot(cons2entropy(u_node, equations), du_node)152end153end154155# calculate surface integral of func(u, equations) * normal on the reference element.156function surface_integral_reference_element(func::Func, u, element,157mesh::Union{TreeMesh{1}, StructuredMesh{1}},158equations, dg::DGSEM,159cache, args...) where {Func}160u_left = get_node_vars(u, equations, dg, 1, element)161u_right = get_node_vars(u, equations, dg, nnodes(dg), element)162163surface_integral = func(u_right, 1, equations) - func(u_left, 1, equations)164return surface_integral165end166167function integrate_via_indices(func::Func, u,168mesh::TreeMesh{1}, equations, dg::DGSEM, cache,169args...; normalize = true) where {Func}170@unpack weights = dg.basis171172# Initialize integral with zeros of the right shape173integral = zero(func(u, 1, 1, equations, dg, args...))174175# Use quadrature to numerically integrate over entire domain176@batch reduction=(+, integral) for element in eachelement(dg, cache)177volume_jacobian_ = volume_jacobian(element, mesh, cache)178for i in eachnode(dg)179integral += volume_jacobian_ * weights[i] *180func(u, i, element, equations, dg, args...)181end182end183184# Normalize with total volume185if normalize186integral = integral / total_volume(mesh)187end188189return integral190end191192function integrate_via_indices(func::Func, u,193mesh::StructuredMesh{1}, equations, dg::DGSEM, cache,194args...; normalize = true) where {Func}195@unpack weights = dg.basis196197# Initialize integral with zeros of the right shape198integral = zero(func(u, 1, 1, equations, dg, args...))199total_volume = zero(real(mesh))200201# Use quadrature to numerically integrate over entire domain202@batch reduction=((+, integral), (+, total_volume)) for element in eachelement(dg,203cache)204for i in eachnode(dg)205jacobian_volume = abs(inv(cache.elements.inverse_jacobian[i, element]))206integral += jacobian_volume * weights[i] *207func(u, i, element, equations, dg, args...)208total_volume += jacobian_volume * weights[i]209end210end211# Normalize with total volume212if normalize213integral = integral / total_volume214end215216return integral217end218219function integrate(func::Func, u,220mesh::Union{TreeMesh{1}, StructuredMesh{1}},221equations, dg::Union{DGSEM, FDSBP}, cache;222normalize = true) where {Func}223integrate_via_indices(u, mesh, equations, dg, cache;224normalize = normalize) do u, i, element, equations, dg225u_local = get_node_vars(u, equations, dg, i, element)226return func(u_local, equations)227end228end229230function analyze(::typeof(entropy_timederivative), du, u, t,231mesh::Union{TreeMesh{1}, StructuredMesh{1}}, equations,232dg::Union{DGSEM, FDSBP}, cache)233# Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ234integrate_via_indices(u, mesh, equations, dg, cache,235du) do u, i, element, equations, dg, du236u_node = get_node_vars(u, equations, dg, i, element)237du_node = get_node_vars(du, equations, dg, i, element)238return dot(cons2entropy(u_node, equations), du_node)239end240end241242function analyze(::Val{:l2_divb}, du, u, t,243mesh::TreeMesh{1}, equations::IdealGlmMhdEquations1D,244dg::DGSEM, cache)245integrate_via_indices(u, mesh, equations, dg, cache,246dg.basis.derivative_matrix) do u, i, element, equations, dg,247derivative_matrix248divb = zero(eltype(u))249for k in eachnode(dg)250divb += derivative_matrix[i, k] * u[6, k, element]251end252divb *= cache.elements.inverse_jacobian[element]253return divb^2254end |> sqrt255end256257function analyze(::Val{:linf_divb}, du, u, t,258mesh::TreeMesh{1}, equations::IdealGlmMhdEquations1D,259dg::DGSEM, cache)260@unpack derivative_matrix, weights = dg.basis261262# integrate over all elements to get the divergence-free condition errors263linf_divb = zero(eltype(u))264@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)265for i in eachnode(dg)266divb = zero(eltype(u))267for k in eachnode(dg)268divb += derivative_matrix[i, k] * u[6, k, element]269end270divb *= cache.elements.inverse_jacobian[element]271linf_divb = max(linf_divb, abs(divb))272end273end274275return linf_divb276end277end # @muladd278279280