Path: blob/main/src/callbacks_step/analysis_dg3d.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{3},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)), StaticInt(nnodes(analyzer)))18u_tmp1 = StrideArray(undef, uEltype,19StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),20StaticInt(nnodes(dg)), StaticInt(nnodes(dg)))21u_tmp2 = StrideArray(undef, uEltype,22StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),23StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))24x_local = StrideArray(undef, RealT,25StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),26StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)))27x_tmp1 = StrideArray(undef, RealT,28StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),29StaticInt(nnodes(dg)), StaticInt(nnodes(dg)))30x_tmp2 = StrideArray(undef, RealT,31StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),32StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))3334return (; u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2)35end3637# Specialized cache for P4estMesh to allow for different ambient dimension from mesh dimension38function create_cache_analysis(analyzer,39mesh::P4estMesh{3, NDIMS_AMBIENT},40equations, dg::DG, cache,41RealT, uEltype) where {NDIMS_AMBIENT}4243# pre-allocate buffers44# We use `StrideArray`s here since these buffers are used in performance-critical45# places and the additional information passed to the compiler makes them faster46# than native `Array`s.47u_local = StrideArray(undef, uEltype,48StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),49StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)))50u_tmp1 = StrideArray(undef, uEltype,51StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),52StaticInt(nnodes(dg)), StaticInt(nnodes(dg)))53u_tmp2 = StrideArray(undef, uEltype,54StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),55StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))56x_local = StrideArray(undef, RealT,57StaticInt(NDIMS_AMBIENT), StaticInt(nnodes(analyzer)),58StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)))59x_tmp1 = StrideArray(undef, RealT,60StaticInt(NDIMS_AMBIENT), StaticInt(nnodes(analyzer)),61StaticInt(nnodes(dg)), StaticInt(nnodes(dg)))62x_tmp2 = StrideArray(undef, RealT,63StaticInt(NDIMS_AMBIENT), StaticInt(nnodes(analyzer)),64StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))65jacobian_local = StrideArray(undef, RealT,66StaticInt(nnodes(analyzer)),67StaticInt(nnodes(analyzer)),68StaticInt(nnodes(analyzer)))69jacobian_tmp1 = StrideArray(undef, RealT,70StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)),71StaticInt(nnodes(dg)))72jacobian_tmp2 = StrideArray(undef, RealT,73StaticInt(nnodes(analyzer)),74StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))7576return (; u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local,77jacobian_tmp1, jacobian_tmp2)78end7980function create_cache_analysis(analyzer,81mesh::Union{StructuredMesh{3}, T8codeMesh{3}},82equations, dg::DG, cache,83RealT, uEltype)8485# pre-allocate buffers86# We use `StrideArray`s here since these buffers are used in performance-critical87# places and the additional information passed to the compiler makes them faster88# than native `Array`s.89u_local = StrideArray(undef, uEltype,90StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),91StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)))92u_tmp1 = StrideArray(undef, uEltype,93StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),94StaticInt(nnodes(dg)), StaticInt(nnodes(dg)))95u_tmp2 = StrideArray(undef, uEltype,96StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),97StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))98x_local = StrideArray(undef, RealT,99StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),100StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)))101x_tmp1 = StrideArray(undef, RealT,102StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),103StaticInt(nnodes(dg)), StaticInt(nnodes(dg)))104x_tmp2 = StrideArray(undef, RealT,105StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),106StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))107jacobian_local = StrideArray(undef, RealT,108StaticInt(nnodes(analyzer)),109StaticInt(nnodes(analyzer)),110StaticInt(nnodes(analyzer)))111jacobian_tmp1 = StrideArray(undef, RealT,112StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)),113StaticInt(nnodes(dg)))114jacobian_tmp2 = StrideArray(undef, RealT,115StaticInt(nnodes(analyzer)),116StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))117118return (; u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local,119jacobian_tmp1, jacobian_tmp2)120end121122function calc_error_norms(func, u, t, analyzer,123mesh::TreeMesh{3}, equations, initial_condition,124dg::DGSEM, cache, cache_analysis)125@unpack vandermonde, weights = analyzer126@unpack node_coordinates = cache.elements127@unpack u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2 = cache_analysis128129# Set up data structures130l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations))131linf_error = copy(l2_error)132133# Iterate over all elements for error calculations134for element in eachelement(dg, cache)135# Interpolate solution and node locations to analysis nodes136multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, :, element),137u_tmp1, u_tmp2)138multiply_dimensionwise!(x_local, vandermonde,139view(node_coordinates, :, :, :, :, element), x_tmp1,140x_tmp2)141142# Calculate errors at each analysis node143volume_jacobian_ = volume_jacobian(element, mesh, cache)144145for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer)146u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j,147k), t, equations)148diff = func(u_exact, equations) -149func(get_node_vars(u_local, equations, dg, i, j, k), equations)150l2_error += diff .^ 2 *151(weights[i] * weights[j] * weights[k] * volume_jacobian_)152linf_error = @. max(linf_error, abs(diff))153end154end155156# For L2 error, divide by total volume157total_volume_ = total_volume(mesh)158l2_error = @. sqrt(l2_error / total_volume_)159160return l2_error, linf_error161end162163function calc_error_norms(func, u, t, analyzer,164mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}},165equations, initial_condition,166dg::DGSEM, cache, cache_analysis)167@unpack vandermonde, weights = analyzer168@unpack u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local, jacobian_tmp1, jacobian_tmp2 = cache_analysis169@unpack node_coordinates, inverse_jacobian = cache.elements170171# Calculate error norms on the CPU, to ensure the order of summation is the same.172if trixi_backend(u) !== nothing173node_coordinates = Array(node_coordinates)174inverse_jacobian = Array(inverse_jacobian)175u = Array(u)176end177178# Set up data structures179l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations))180linf_error = copy(l2_error)181total_volume = zero(real(mesh))182183# Iterate over all elements for error calculations184for element in eachelement(dg, cache)185# Interpolate solution and node locations to analysis nodes186multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, :, element),187u_tmp1, u_tmp2)188multiply_dimensionwise!(x_local, vandermonde,189view(node_coordinates, :, :, :, :, element), x_tmp1,190x_tmp2)191multiply_scalar_dimensionwise!(jacobian_local, vandermonde,192inv.(view(inverse_jacobian, :, :, :, element)),193jacobian_tmp1, jacobian_tmp2)194195# Calculate errors at each analysis node196for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer)197u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j,198k), t, equations)199diff = func(u_exact, equations) -200func(get_node_vars(u_local, equations, dg, i, j, k), equations)201# We take absolute value as we need the Jacobian here for the volume calculation202abs_jacobian_local_ijk = abs(jacobian_local[i, j, k])203204l2_error += diff .^ 2 *205(weights[i] * weights[j] * weights[k] * abs_jacobian_local_ijk)206linf_error = @. max(linf_error, abs(diff))207total_volume += (weights[i] * weights[j] * weights[k] *208abs_jacobian_local_ijk)209end210end211212# For L2 error, divide by total volume213l2_error = @. sqrt(l2_error / total_volume)214215return l2_error, linf_error216end217218# Use quadrature to numerically integrate a single element.219# We do not multiply by the Jacobian to stay in reference space.220# This avoids the need to divide the RHS of the DG scheme by the Jacobian when computing221# the time derivative of entropy, see `entropy_change_reference_element`.222function integrate_reference_element(func::Func, u, element,223::Type{<:AbstractMesh{3}}, equations, dg::DGSEM,224cache,225args...; normalize = true) where {Func}226@unpack weights = dg.basis227228# Initialize integral with zeros of the right shape229integral = zero(func(u, 1, 1, 1, element, equations, dg, args...))230231for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)232integral += weights[i] * weights[j] * weights[k] *233func(u, i, j, k, element, equations, dg, args...)234end235236return integral237end238239# Calculate ∫_e (∂S/∂u ⋅ ∂u/∂t) dΩ_e where the result on element 'e' is kept in reference space240# Note that ∂S/∂u = w(u) with entropy variables w241function entropy_change_reference_element(du, u, element,242MeshT::Type{<:AbstractMesh{3}},243equations, dg::DGSEM, cache, args...)244return integrate_reference_element(u, element, MeshT, equations, dg, cache,245du) do u, i, j, k, element, equations, dg, du246u_node = get_node_vars(u, equations, dg, i, j, k, element)247du_node = get_node_vars(du, equations, dg, i, j, k, element)248249dot(cons2entropy(u_node, equations), du_node)250end251end252253# calculate surface integral of func(u, equations) * normal on the reference element.254function surface_integral_reference_element(func::Func, u, element,255::Type{<:TreeMesh{3}}, equations, dg::DGSEM,256cache, args...) where {Func}257@unpack weights = dg.basis258259u_tmp = get_node_vars(u, equations, dg, 1, 1, 1, element)260surface_integral = zero(func(u_tmp, 1, equations))261for j in eachnode(dg), i in eachnode(dg)262# integrate along x-y plane, normal in z (3) direction263u_back = get_node_vars(u, equations, dg, i, j, 1, element)264u_front = get_node_vars(u, equations, dg, i, j, nnodes(dg), element)265266surface_integral += dg.basis.weights[i] * dg.basis.weights[j] *267(func(u_front, 3, equations) - func(u_back, 3, equations))268269# integrate along x-z plane, normal in y (2) direction270u_bottom = get_node_vars(u, equations, dg, i, 1, j, element)271u_top = get_node_vars(u, equations, dg, i, nnodes(dg), j, element)272273surface_integral += dg.basis.weights[i] * dg.basis.weights[j] *274(func(u_top, 2, equations) - func(u_bottom, 2, equations))275276# integrate along y-z plane, normal in x (1) direction277u_left = get_node_vars(u, equations, dg, 1, i, j, element)278u_right = get_node_vars(u, equations, dg, nnodes(dg), i, j, element)279280surface_integral += dg.basis.weights[i] * dg.basis.weights[j] *281(func(u_right, 1, equations) - func(u_left, 1, equations))282end283284return surface_integral285end286287# calculate surface integral of func(u, normal_direction, equations) * normal on the reference element.288# Note: `get_normal_direction` already returns an outward-pointing normal for all directions,289# thus no +- flips are needed here.290function surface_integral_reference_element(func::Func, u, element,291::Type{<:Union{StructuredMesh{3},292P4estMesh{3},293T8codeMesh{3}}},294equations, dg::DGSEM, cache,295args...) where {Func}296@unpack contravariant_vectors = cache.elements297@unpack weights = dg.basis298299# Construct zero of right shape:300# Evaluate `func` at actual quadrature node and normal direction301u_tmp = get_node_vars(u, equations, dg, 1, 1, 1, element)302normal_direction = get_normal_direction(1, contravariant_vectors,3031, 1, 1, element)304surface_integral = zero(func(u_tmp, normal_direction, equations))305306# Direction 1: face at i = 1 (x_min)307for k in eachnode(dg), j in eachnode(dg)308u_node = get_node_vars(u, equations, dg, 1, j, k, element)309normal_direction = get_normal_direction(1, contravariant_vectors,3101, j, k, element)311surface_integral += weights[j] * weights[k] *312func(u_node, normal_direction, equations)313end314315# Direction 2: face at i = nnodes(dg) (x_max)316for k in eachnode(dg), j in eachnode(dg)317u_node = get_node_vars(u, equations, dg, nnodes(dg), j, k, element)318normal_direction = get_normal_direction(2, contravariant_vectors,319nnodes(dg), j, k, element)320surface_integral += weights[j] * weights[k] *321func(u_node, normal_direction, equations)322end323324# Direction 3: face at j = 1 (y_min)325for k in eachnode(dg), i in eachnode(dg)326u_node = get_node_vars(u, equations, dg, i, 1, k, element)327normal_direction = get_normal_direction(3, contravariant_vectors,328i, 1, k, element)329surface_integral += weights[i] * weights[k] *330func(u_node, normal_direction, equations)331end332333# Direction 4: face at j = nnodes(dg) (y_max)334for k in eachnode(dg), i in eachnode(dg)335u_node = get_node_vars(u, equations, dg, i, nnodes(dg), k, element)336normal_direction = get_normal_direction(4, contravariant_vectors,337i, nnodes(dg), k, element)338surface_integral += weights[i] * weights[k] *339func(u_node, normal_direction, equations)340end341342# Direction 5: face at k = 1 (z_min)343for j in eachnode(dg), i in eachnode(dg)344u_node = get_node_vars(u, equations, dg, i, j, 1, element)345normal_direction = get_normal_direction(5, contravariant_vectors,346i, j, 1, element)347surface_integral += weights[i] * weights[j] *348func(u_node, normal_direction, equations)349end350351# Direction 6: face at k = nnodes(dg) (z_max)352for j in eachnode(dg), i in eachnode(dg)353u_node = get_node_vars(u, equations, dg, i, j, nnodes(dg), element)354normal_direction = get_normal_direction(6, contravariant_vectors,355i, j, nnodes(dg), element)356surface_integral += weights[i] * weights[j] *357func(u_node, normal_direction, equations)358end359360return surface_integral361end362363function integrate_via_indices(func::Func, u,364mesh::TreeMesh{3}, equations, dg::DGSEM, cache,365args...; normalize = true) where {Func}366@unpack weights = dg.basis367368# Initialize integral with zeros of the right shape369integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...))370371# Use quadrature to numerically integrate over entire domain372@batch reduction=(+, integral) for element in eachelement(dg, cache)373volume_jacobian_ = volume_jacobian(element, mesh, cache)374for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)375integral += volume_jacobian_ * weights[i] * weights[j] * weights[k] *376func(u, i, j, k, element, equations, dg, args...)377end378end379380# Normalize with total volume381if normalize382integral = integral / total_volume(mesh)383end384385return integral386end387388function integrate_via_indices(func::Func, u,389mesh::Union{StructuredMesh{3}, P4estMesh{3},390T8codeMesh{3}},391equations, dg::DGSEM, cache,392args...; normalize = true) where {Func}393return integrate_via_indices(func, trixi_backend(u), u, mesh, equations, dg, cache,394args...; normalize = normalize)395end396397function integrate_via_indices(func::Func, ::Nothing, u,398mesh::Union{StructuredMesh{3}, P4estMesh{3},399T8codeMesh{3}},400equations, dg::DGSEM, cache,401args...; normalize = true) where {Func}402@unpack weights = dg.basis403@unpack inverse_jacobian = cache.elements404405# Initialize integral with zeros of the right shape406integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...))407total_volume = zero(real(mesh))408409# Use quadrature to numerically integrate over entire domain410@batch reduction=((+, integral), (+, total_volume)) for element in eachelement(dg,411cache)412for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)413volume_jacobian = abs(inv(inverse_jacobian[i, j, k, element]))414integral += volume_jacobian * weights[i] * weights[j] * weights[k] *415func(u, i, j, k, element, equations, dg, args...)416total_volume += volume_jacobian * weights[i] * weights[j] * weights[k]417end418end419420# Normalize with total volume421if normalize422integral = integral / total_volume423end424425return integral426end427428function integrate_via_indices(func::Func, backend::Backend, u,429mesh::Union{StructuredMesh{3}, P4estMesh{3},430T8codeMesh{3}},431equations, dg::DGSEM, cache,432args...; normalize = true) where {Func}433@unpack weights = dg.basis434@unpack inverse_jacobian = cache.elements435436function local_plus((integral, total_volume), (integral_element, volume_element))437return (integral + integral_element, total_volume + volume_element)438end439440# `func(u, 1,1,1,1, equations, dg, args...)` might access GPU memory441# so we have to rely on the compiler to correctly infer the type of the integral here.442# TODO: Technically we need device_promote_op here that "infers" the function within the context of the GPU.443integral0 = zero(Base.promote_op(func, typeof(u), Int, Int, Int, Int,444typeof(equations), typeof(dg),445map(typeof, args)...))446init = neutral = (integral0, zero(real(mesh)))447448# Use quadrature to numerically integrate over entire domain449num_elements = nelements(dg, cache)450integral, total_volume = AcceleratedKernels.mapreduce(local_plus, 1:num_elements,451backend; init,452neutral) do element453# Initialize integral with zeros of the right shape454local_integral, local_total_volume = neutral455456for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)457volume_jacobian = abs(inv(inverse_jacobian[i, j, k, element]))458local_integral += volume_jacobian * weights[i] * weights[j] * weights[k] *459func(u, i, j, k, element, equations, dg, args...)460local_total_volume += volume_jacobian * weights[i] * weights[j] * weights[k]461end462return local_integral, local_total_volume463end464465# Normalize with total volume466if normalize467integral = integral / total_volume468end469470return integral471end472473function integrate(func::Func, u,474mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3},475T8codeMesh{3}},476equations, dg::Union{DGSEM, FDSBP}, cache;477normalize = true) where {Func}478integrate_via_indices(u, mesh, equations, dg, cache;479normalize = normalize) do u, i, j, k, element, equations, dg480u_local = get_node_vars(u, equations, dg, i, j, k, element)481return func(u_local, equations)482end483end484485function integrate(func::Func, u,486mesh::Union{TreeMesh{3}, P4estMesh{3}},487equations, equations_parabolic, dg::DGSEM,488cache, cache_parabolic; normalize = true) where {Func}489gradients_x, gradients_y, gradients_z = cache_parabolic.parabolic_container.gradients490integrate_via_indices(u, mesh, equations, dg, cache;491normalize = normalize) do u, i, j, k, element, equations, dg492u_local = get_node_vars(u, equations, dg, i, j, k, element)493gradients_1_local = get_node_vars(gradients_x, equations_parabolic, dg, i, j, k,494element)495gradients_2_local = get_node_vars(gradients_y, equations_parabolic, dg, i, j, k,496element)497gradients_3_local = get_node_vars(gradients_z, equations_parabolic, dg, i, j, k,498element)499return func(u_local, (gradients_1_local, gradients_2_local, gradients_3_local),500equations_parabolic)501end502end503504function analyze(::typeof(entropy_timederivative), du, u, t,505mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3},506T8codeMesh{3}},507equations, dg::Union{DGSEM, FDSBP}, cache)508# Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ509integrate_via_indices(u, mesh, equations, dg, cache,510du) do u, i, j, k, element, equations, dg, du511u_node = get_node_vars(u, equations, dg, i, j, k, element)512du_node = get_node_vars(du, equations, dg, i, j, k, element)513return dot(cons2entropy(u_node, equations), du_node)514end515end516517function analyze(::Val{:l2_divb}, du, u, t,518mesh::TreeMesh{3}, equations,519dg::DGSEM, cache)520integrate_via_indices(u, mesh, equations, dg, cache, cache,521dg.basis.derivative_matrix) do u, i, j, k, element, equations,522dg, cache, derivative_matrix523divb = zero(eltype(u))524for l in eachnode(dg)525u_ljk = get_node_vars(u, equations, dg, l, j, k, element)526u_ilk = get_node_vars(u, equations, dg, i, l, k, element)527u_ijl = get_node_vars(u, equations, dg, i, j, l, element)528529B_ljk = magnetic_field(u_ljk, equations)530B_ilk = magnetic_field(u_ilk, equations)531B_ijl = magnetic_field(u_ijl, equations)532533divb += (derivative_matrix[i, l] * B_ljk[1] +534derivative_matrix[j, l] * B_ilk[2] +535derivative_matrix[k, l] * B_ijl[3])536end537divb *= cache.elements.inverse_jacobian[element]538return divb^2539end |> sqrt540end541542function analyze(::Val{:l2_divb}, du, u, t,543mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}},544equations,545dg::DGSEM, cache)546@unpack contravariant_vectors, inverse_jacobian = cache.elements547integrate_via_indices(u, mesh, equations, dg, cache, cache,548dg.basis.derivative_matrix) do u, i, j, k, element, equations,549dg, cache, derivative_matrix550divb = zero(eltype(u))551# Get the contravariant vectors Ja^1, Ja^2, and Ja^3552Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors,553i, j, k, element)554Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors,555i, j, k, element)556Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors,557i, j, k, element)558# Compute the transformed divergence559for l in eachnode(dg)560u_ljk = get_node_vars(u, equations, dg, l, j, k, element)561u_ilk = get_node_vars(u, equations, dg, i, l, k, element)562u_ijl = get_node_vars(u, equations, dg, i, j, l, element)563564B_ljk = magnetic_field(u_ljk, equations)565B_ilk = magnetic_field(u_ilk, equations)566B_ijl = magnetic_field(u_ijl, equations)567568divb += (derivative_matrix[i, l] *569(Ja11 * B_ljk[1] + Ja12 * B_ljk[2] + Ja13 * B_ljk[3]) +570derivative_matrix[j, l] *571(Ja21 * B_ilk[1] + Ja22 * B_ilk[2] + Ja23 * B_ilk[3]) +572derivative_matrix[k, l] *573(Ja31 * B_ijl[1] + Ja32 * B_ijl[2] + Ja33 * B_ijl[3]))574end575divb *= inverse_jacobian[i, j, k, element]576return divb^2577end |> sqrt578end579580function analyze(::Val{:linf_divb}, du, u, t,581mesh::TreeMesh{3}, equations,582dg::DGSEM, cache)583@unpack derivative_matrix, weights = dg.basis584585# integrate over all elements to get the divergence-free condition errors586linf_divb = zero(eltype(u))587@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)588for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)589divb = zero(eltype(u))590for l in eachnode(dg)591u_ljk = get_node_vars(u, equations, dg, l, j, k, element)592u_ilk = get_node_vars(u, equations, dg, i, l, k, element)593u_ijl = get_node_vars(u, equations, dg, i, j, l, element)594595B_ljk = magnetic_field(u_ljk, equations)596B_ilk = magnetic_field(u_ilk, equations)597B_ijl = magnetic_field(u_ijl, equations)598599divb += (derivative_matrix[i, l] * B_ljk[1] +600derivative_matrix[j, l] * B_ilk[2] +601derivative_matrix[k, l] * B_ijl[3])602end603divb *= cache.elements.inverse_jacobian[element]604linf_divb = max(linf_divb, abs(divb))605end606end607608if mpi_isparallel()609# Base.max instead of max needed, see comment in src/auxiliary/math.jl610linf_divb = MPI.Allreduce!(Ref(linf_divb), Base.max, mpi_comm())[]611end612613return linf_divb614end615616function analyze(::Val{:linf_divb}, du, u, t,617mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}},618equations,619dg::DGSEM, cache)620@unpack derivative_matrix, weights = dg.basis621@unpack contravariant_vectors, inverse_jacobian = cache.elements622623# integrate over all elements to get the divergence-free condition errors624linf_divb = zero(eltype(u))625@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)626for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)627divb = zero(eltype(u))628# Get the contravariant vectors Ja^1, Ja^2, and Ja^3629Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors,630i, j, k, element)631Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors,632i, j, k, element)633Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors,634i, j, k, element)635# Compute the transformed divergence636for l in eachnode(dg)637u_ljk = get_node_vars(u, equations, dg, l, j, k, element)638u_ilk = get_node_vars(u, equations, dg, i, l, k, element)639u_ijl = get_node_vars(u, equations, dg, i, j, l, element)640641B_ljk = magnetic_field(u_ljk, equations)642B_ilk = magnetic_field(u_ilk, equations)643B_ijl = magnetic_field(u_ijl, equations)644645divb += (derivative_matrix[i, l] * (Ja11 * B_ljk[1] +646Ja12 * B_ljk[2] + Ja13 * B_ljk[3]) +647derivative_matrix[j, l] * (Ja21 * B_ilk[1] +648Ja22 * B_ilk[2] + Ja23 * B_ilk[3]) +649derivative_matrix[k, l] * (Ja31 * B_ijl[1] +650Ja32 * B_ijl[2] + Ja33 * B_ijl[3]))651end652divb *= inverse_jacobian[i, j, k, element]653linf_divb = max(linf_divb, abs(divb))654end655end656657if mpi_isparallel()658# Base.max instead of max needed, see comment in src/auxiliary/math.jl659linf_divb = MPI.Allreduce!(Ref(linf_divb), Base.max, mpi_comm())[]660end661662return linf_divb663end664end # @muladd665666667