@muladd begin
mul_by!(A) = @inline (out, x) -> matmul!(out, A, x)
mul_by!(A::T) where {T <: SimpleKronecker} = @inline (out, x) -> mul!(out, A, x)
mul_by!(A::AbstractSparseMatrix) = @inline (out, x) -> mul!(out, A, x)
function mul_by!(A::LinearAlgebra.AdjOrTrans{T, S}) where {T, S <: AbstractSparseMatrix}
@inline (out, x) -> mul!(out, A, x)
end
mul_by_accum!(A, α) = @inline (out, x) -> matmul!(out, A, x, α, One())
function mul_by_accum!(A::AbstractSparseMatrix, α)
@inline (out, x) -> mul!(out, A, x, α, One())
end
mul_by_accum!(A) = mul_by_accum!(A, One())
struct MulByUniformScaling end
struct MulByAccumUniformScaling end
mul_by!(A::UniformScaling) = MulByUniformScaling()
mul_by_accum!(A::UniformScaling) = MulByAccumUniformScaling()
@inline function apply_to_each_field(f::F, args::Vararg{Any, N}) where {F, N}
return StructArrays.foreachfield(f, args...)
end
@inline apply_to_each_field(f::MulByUniformScaling, out, x, args...) = copy!(out, x)
@inline function apply_to_each_field(f::MulByAccumUniformScaling, out, x, args...)
@threaded for i in eachindex(x)
out[i] = out[i] + x[i]
end
end
@inline nelements(dg::DGMulti, cache) = size(cache.solution_container.u_values)[end]
@inline function sparse_operator_data(A::Union{<:SparseMatrixCSC,
<:Adjoint{<:Any, <:SparseMatrixCSC}})
A_base = parent(A)
return A_base, axes(A, 2), rowvals(A_base), nonzeros(A_base)
end
"""
eachdim(mesh)
Return an iterator over the indices that specify the location in relevant data structures
for the dimensions in `AbstractTree`.
In particular, not the dimensions themselves are returned.
"""
@inline eachdim(mesh) = Base.OneTo(ndims(mesh))
@inline function ndofs(mesh::DGMultiMesh, dg::DGMulti, other_args...)
return dg.basis.Np * mesh.md.num_elements
end
"""
eachelement(mesh::DGMultiMesh, dg::DGMulti, other_args...)
Return an iterator over the indices that specify the location in relevant data structures
for the elements in `mesh`.
In particular, not the elements themselves are returned.
"""
@inline function eachelement(mesh::DGMultiMesh, dg::DGMulti, other_args...)
return Base.OneTo(mesh.md.num_elements)
end
@inline nnodes(basis::RefElemData) = basis.Np
"""
each_face_node(mesh::DGMultiMesh, dg::DGMulti, other_args...)
Return an iterator over the indices that specify the location in relevant data structures
for the face nodes in `dg`.
In particular, not the face_nodes themselves are returned.
"""
@inline function each_face_node(mesh::DGMultiMesh, dg::DGMulti, other_args...)
return Base.OneTo(dg.basis.Nfq)
end
"""
each_quad_node(mesh::DGMultiMesh, dg::DGMulti, other_args...)
Return an iterator over the indices that specify the location in relevant data structures
for the quadrature nodes in `dg`.
In particular, not the quadrature nodes themselves are returned.
"""
@inline function each_quad_node(mesh::DGMultiMesh, dg::DGMulti, other_args...)
return Base.OneTo(dg.basis.Nq)
end
"""
each_dof_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
Return an iterator over the indices that specify the location in relevant data structures
for the degrees of freedom (DOF) in `dg`.
In particular, not the DOFs themselves are returned.
"""
@inline function each_dof_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
return Base.OneTo(ndofs(mesh, dg, other_args...))
end
"""
each_quad_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
Return an iterator over the indices that specify the location in relevant data structures
for the global quadrature nodes in `mesh`.
In particular, not the quadrature nodes themselves are returned.
"""
@inline function each_quad_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
return Base.OneTo(dg.basis.Nq * mesh.md.num_elements)
end
"""
each_face_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
Return an iterator over the indices that specify the location in relevant data structures
for the face nodes in `mesh`.
In particular, not the face nodes themselves are returned.
"""
@inline function each_face_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
return Base.OneTo(dg.basis.Nfq * mesh.md.num_elements)
end
@inline nmodes(N, ::Line) = N + 1
@inline nmodes(N, ::Quad) = (N + 1)^2
@inline nmodes(N, ::Hex) = (N + 1)^3
wrap_array(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode
wrap_array_native(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode
function allocate_coefficients(mesh::DGMultiMesh, equations, dg::DGMulti, cache)
return VectorOfArray(allocate_nested_array(real(dg), nvariables(equations),
size(mesh.md.x), dg))
end
wrap_array(u_ode::VectorOfArray, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = parent(u_ode)
function digest_boundary_conditions(boundary_conditions::NamedTuple{Keys, ValueTypes},
mesh::DGMultiMesh, dg::DGMulti,
cache) where {Keys, ValueTypes <: NTuple{N, Any}
} where {N}
return boundary_conditions
end
function allocate_nested_array(uEltype, nvars, array_dimensions, dg)
return StructArray{SVector{nvars, uEltype}}(ntuple(_ -> zeros(uEltype,
array_dimensions...),
nvars))
end
function set_zero!(du, dg::DGMulti, other_args...)
@threaded for i in eachindex(du)
du[i] = zero(eltype(du))
end
return nothing
end
struct DGMultiSolutionContainer{uType, ufType, ffType, lType}
u_values::uType
u_face_values::ufType
flux_face_values::ffType
local_values_threaded::lType
end
function initialize_dgmulti_solution_container(mesh::DGMultiMesh, equations,
dg::DGMulti,
uEltype)
rd = dg.basis
md = mesh.md
nvars = nvariables(equations)
u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg)
u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg)
flux_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg)
local_values_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg)
for _ in 1:Threads.maxthreadid()]
return DGMultiSolutionContainer(u_values, u_face_values, flux_face_values,
local_values_threaded)
end
function create_cache(mesh::DGMultiMesh{NDIMS}, equations, dg::DGMultiWeakForm, RealT,
uEltype) where {NDIMS}
rd = dg.basis
md = mesh.md
@unpack wq, Vq, M, Drst = rd
weak_differentiation_matrices = map(D -> -M \ ((Vq * D)' * Diagonal(wq)), Drst)
if typeof(rd.approximation_type) <:
Union{SBP, AbstractNonperiodicDerivativeOperator}
lift_scalings = rd.wf ./ rd.wq[rd.Fmask]
else
lift_scalings = nothing
end
dxidxhatj = map(x -> rd.Vq * x, md.rstxyzJ)
invJ = inv.(rd.Vq * md.J)
nvars = nvariables(equations)
flux_threaded = [[allocate_nested_array(uEltype, nvars, (rd.Nq,), dg)
for _ in 1:NDIMS] for _ in 1:Threads.maxthreadid()]
rotated_flux_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg)
for _ in 1:Threads.maxthreadid()]
solution_container = initialize_dgmulti_solution_container(mesh, equations, dg,
uEltype)
return (; md, weak_differentiation_matrices, lift_scalings, invJ, dxidxhatj,
solution_container, flux_threaded, rotated_flux_threaded)
end
function compute_coefficients!(::Nothing, u, initial_condition, t,
mesh::DGMultiMesh, equations, dg::DGMulti, cache)
md = mesh.md
rd = dg.basis
(; u_values) = cache.solution_container
@threaded for i in each_quad_node_global(mesh, dg, cache)
u_values[i] = initial_condition(SVector(getindex.(md.xyzq, i)),
t, equations)
end
apply_to_each_field(mul_by!(rd.Pq), u, u_values)
return nothing
end
function estimate_dt(mesh::DGMultiMesh, dg::DGMulti)
rd = dg.basis
return StartUpDG.estimate_h(rd, mesh.md) / StartUpDG.inverse_trace_constant(rd)
end
dt_polydeg_scaling(dg::DGMulti) = inv(dg.basis.N + 1)
function dt_polydeg_scaling(dg::DGMulti{3, <:Wedge, <:TensorProductWedge})
return inv(maximum(dg.basis.N) + 1)
end
function max_dt(u, t, mesh::DGMultiMesh,
constant_diffusivity::False, equations,
equations_parabolic::AbstractEquationsParabolic,
dg::DGMulti{NDIMS},
cache) where {NDIMS}
@unpack md = mesh
rd = dg.basis
dt_min = floatmax(typeof(t))
for e in eachelement(mesh, dg, cache)
h_e = StartUpDG.estimate_h(e, rd, md)
max_speeds = ntuple(_ -> nextfloat(zero(t)), NDIMS)
for i in Base.OneTo(rd.Np)
lambda_i = max_abs_speeds(u[i, e], equations)
diffusivity = max_diffusivity(u[i, e], equations_parabolic)
max_speeds = max.(max_speeds, lambda_i, diffusivity / h_e)
end
dt_min = min(dt_min, h_e / sum(max_speeds))
end
return 2 * dt_min * dt_polydeg_scaling(dg)
end
function max_dt(u, t, mesh::DGMultiMesh,
constant_diffusivity::True, equations,
equations_parabolic::AbstractEquationsParabolic,
dg::DGMulti{NDIMS},
cache) where {NDIMS}
@unpack md = mesh
rd = dg.basis
max_speeds = max_abs_speeds(equations)
diffusivity = max_diffusivity(equations_parabolic)
dt_min = floatmax(typeof(t))
for e in eachelement(mesh, dg, cache)
h_e = StartUpDG.estimate_h(e, rd, md)
max_speeds = max.(max_speeds, diffusivity / h_e)
dt_min = min(dt_min, h_e / sum(max_speeds))
end
return 2 * dt_min * dt_polydeg_scaling(dg)
end
function max_dt(u, t, mesh::DGMultiMesh,
constant_speed::False, equations, dg::DGMulti{NDIMS},
cache) where {NDIMS}
@unpack md = mesh
rd = dg.basis
dt_min = floatmax(typeof(t))
for e in eachelement(mesh, dg, cache)
h_e = StartUpDG.estimate_h(e, rd, md)
max_speeds = ntuple(_ -> nextfloat(zero(t)), NDIMS)
for i in Base.OneTo(rd.Np)
lambda_i = max_abs_speeds(u[i, e], equations)
max_speeds = max.(max_speeds, lambda_i)
end
dt_min = min(dt_min, h_e / sum(max_speeds))
end
return 2 * dt_min * dt_polydeg_scaling(dg)
end
function max_dt(u, t, mesh::DGMultiMesh,
constant_speed::True, equations, dg::DGMulti{NDIMS},
cache) where {NDIMS}
@unpack md = mesh
rd = dg.basis
max_speeds = max_abs_speeds(equations)
dt_min = floatmax(typeof(t))
for e in eachelement(mesh, dg, cache)
h_e = StartUpDG.estimate_h(e, rd, md)
dt_min = min(dt_min, h_e / sum(max_speeds))
end
return 2 * dt_min * dt_polydeg_scaling(dg)
end
function prolong2interfaces!(cache, u,
mesh::DGMultiMesh, equations, dg::DGMulti)
rd = dg.basis
(; u_face_values) = cache.solution_container
apply_to_each_field(mul_by!(rd.Vf), u_face_values, u)
return nothing
end
@inline function volume_integral_kernel!(du, u, element, mesh::DGMultiMesh,
have_nonconservative_terms::False, equations,
volume_integral::VolumeIntegralWeakForm,
dg::DGMulti, cache)
@unpack weak_differentiation_matrices, dxidxhatj = cache
(; u_values, local_values_threaded) = cache.solution_container
flux_values = local_values_threaded[Threads.threadid()]
for i in eachdim(mesh)
for j in eachindex(flux_values)
flux_values[j] = flux(u_values[j, element], i, equations)
end
for j in eachdim(mesh)
apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j],
dxidxhatj[i, j][1, element]),
view(du, :, element), flux_values)
end
end
return nothing
end
@inline function volume_integral_kernel!(du, u, element,
mesh::DGMultiMesh{NDIMS, <:NonAffine},
have_nonconservative_terms::False, equations,
volume_integral::VolumeIntegralWeakForm,
dg::DGMulti, cache) where {NDIMS}
(; weak_differentiation_matrices, dxidxhatj) = cache
(; u_values) = cache.solution_container
flux_values = cache.flux_threaded[Threads.threadid()]
for i in eachdim(mesh)
flux_values[i] .= flux.(view(u_values, :, element), i, equations)
end
rotated_flux_values = cache.rotated_flux_threaded[Threads.threadid()]
for j in eachdim(mesh)
fill!(rotated_flux_values, zero(eltype(rotated_flux_values)))
for i in eachdim(mesh)
for ii in eachindex(rotated_flux_values)
flux_i_node = flux_values[i][ii]
dxidxhatj_node = dxidxhatj[i, j][ii, element]
rotated_flux_values[ii] = rotated_flux_values[ii] +
dxidxhatj_node * flux_i_node
end
end
apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j]),
view(du, :, element), rotated_flux_values)
end
return nothing
end
function calc_volume_integral!(du, u, mesh::DGMultiMesh,
have_nonconservative_terms, equations,
volume_integral::VolumeIntegralWeakForm, dg::DGMulti,
cache)
rd = dg.basis
(; u_values) = cache.solution_container
apply_to_each_field(mul_by!(rd.Vq), u_values, u)
@threaded for element in eachelement(mesh, dg, cache)
volume_integral_kernel!(du, u, element, mesh,
have_nonconservative_terms, equations,
volume_integral, dg, cache)
end
return nothing
end
function calc_interface_flux!(cache, surface_integral::SurfaceIntegralWeakForm,
mesh::DGMultiMesh,
have_nonconservative_terms::False, equations,
dg::DGMulti{NDIMS}) where {NDIMS}
@unpack surface_flux = surface_integral
md = mesh.md
@unpack mapM, mapP, nxyzJ, Jf = md
(; u_face_values, flux_face_values) = cache.solution_container
@threaded for face_node_index in each_face_node_global(mesh, dg, cache)
idM, idP = mapM[face_node_index], mapP[face_node_index]
uM = u_face_values[idM]
uP = u_face_values[idP]
normal = SVector{NDIMS}(getindex.(nxyzJ, idM)) / Jf[idM]
flux_face_values[idM] = surface_flux(uM, uP, normal, equations) * Jf[idM]
end
return nothing
end
function calc_interface_flux!(cache, surface_integral::SurfaceIntegralWeakForm,
mesh::DGMultiMesh,
have_nonconservative_terms::True, equations,
dg::DGMulti{NDIMS}) where {NDIMS}
flux_conservative, flux_nonconservative = surface_integral.surface_flux
md = mesh.md
@unpack mapM, mapP, nxyzJ, Jf = md
(; u_face_values, flux_face_values) = cache.solution_container
@threaded for face_node_index in each_face_node_global(mesh, dg, cache)
idM, idP = mapM[face_node_index], mapP[face_node_index]
uM = u_face_values[idM]
if idM != idP
uP = u_face_values[idP]
normal = SVector{NDIMS}(getindex.(nxyzJ, idM)) / Jf[idM]
conservative_part = flux_conservative(uM, uP, normal, equations)
nonconservative_part = flux_nonconservative(uM, uP, normal, equations)
flux_face_values[idM] = (conservative_part + 0.5 * nonconservative_part) *
Jf[idM]
end
end
return nothing
end
function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations,
surface_integral::SurfaceIntegralWeakForm,
dg::DGMulti, cache)
rd = dg.basis
apply_to_each_field(mul_by_accum!(rd.LIFT), du,
cache.solution_container.flux_face_values)
return nothing
end
function prolong2interfaces!(cache, u,
mesh::DGMultiMesh, equations, dg::DGMultiSBP)
rd = dg.basis
@unpack Fmask = rd
(; u_face_values) = cache.solution_container
@threaded for e in eachelement(mesh, dg, cache)
for (i, fid) in enumerate(Fmask)
u_face_values[i, e] = u[fid, e]
end
end
return nothing
end
function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations,
surface_integral::SurfaceIntegralWeakForm,
dg::DGMultiSBP, cache)
rd = dg.basis
(; flux_face_values) = cache.solution_container
@unpack lift_scalings = cache
@threaded for e in eachelement(mesh, dg, cache)
for i in each_face_node(mesh, dg, cache)
fid = rd.Fmask[i]
du[fid, e] = du[fid, e] + flux_face_values[i, e] * lift_scalings[i]
end
end
return nothing
end
function calc_boundary_flux!(cache, t, boundary_conditions::BoundaryConditionPeriodic,
mesh, have_nonconservative_terms, equations, dg::DGMulti)
return nothing
end
function calc_boundary_flux!(cache, t, boundary_conditions, mesh,
have_nonconservative_terms, equations, dg::DGMulti)
for (key, value) in zip(keys(boundary_conditions), boundary_conditions)
calc_single_boundary_flux!(cache, t, value,
key,
mesh, have_nonconservative_terms, equations, dg)
end
return nothing
end
function calc_single_boundary_flux!(cache, t, boundary_condition, boundary_key, mesh,
have_nonconservative_terms::False, equations,
dg::DGMulti{NDIMS}) where {NDIMS}
rd = dg.basis
md = mesh.md
(; u_face_values, flux_face_values) = cache.solution_container
@unpack xyzf, nxyzJ, Jf = md
@unpack surface_flux = dg.surface_integral
num_faces = StartUpDG.num_faces(rd.element_type)
num_pts_per_face = rd.Nfq ÷ num_faces
num_faces_total = num_faces * md.num_elements
reshape_by_face(u) = Base.ReshapedArray(u, (num_pts_per_face, num_faces_total), ())
u_face_values = reshape_by_face(u_face_values)
flux_face_values = reshape_by_face(flux_face_values)
Jf = reshape_by_face(Jf)
nxyzJ, xyzf = reshape_by_face.(nxyzJ), reshape_by_face.(xyzf)
for f in mesh.boundary_faces[boundary_key]
for i in Base.OneTo(num_pts_per_face)
face_normal = SVector{NDIMS}(getindex.(nxyzJ, i, f)) / Jf[i, f]
face_coordinates = SVector{NDIMS}(getindex.(xyzf, i, f))
flux_face_values[i, f] = boundary_condition(u_face_values[i, f],
face_normal, face_coordinates,
t,
surface_flux, equations) *
Jf[i, f]
end
end
return nothing
end
function calc_single_boundary_flux!(cache, t, boundary_condition, boundary_key, mesh,
have_nonconservative_terms::True, equations,
dg::DGMulti{NDIMS}) where {NDIMS}
rd = dg.basis
md = mesh.md
num_pts_per_face = rd.Nfq ÷ StartUpDG.num_faces(rd.element_type)
num_faces_total = StartUpDG.num_faces(rd.element_type) * md.num_elements
reshape_by_face(u) = Base.ReshapedArray(u, (num_pts_per_face, num_faces_total), ())
u_face_values = reshape_by_face(cache.solution_container.u_face_values)
flux_face_values = reshape_by_face(cache.solution_container.flux_face_values)
Jf = reshape_by_face(md.Jf)
nxyzJ, xyzf = reshape_by_face.(md.nxyzJ), reshape_by_face.(md.xyzf)
for f in mesh.boundary_faces[boundary_key]
for i in Base.OneTo(num_pts_per_face)
face_normal = SVector{NDIMS}(getindex.(nxyzJ, i, f)) / Jf[i, f]
face_coordinates = SVector{NDIMS}(getindex.(xyzf, i, f))
cons_flux_at_face_node, noncons_flux_at_face_node = boundary_condition(u_face_values[i,
f],
face_normal,
face_coordinates,
t,
dg.surface_integral.surface_flux,
equations)
flux_face_values[i, f] = (cons_flux_at_face_node +
0.5f0 * noncons_flux_at_face_node) * Jf[i, f]
end
end
return nothing
end
function invert_jacobian!(du, mesh::DGMultiMesh, equations, dg::DGMulti, cache;
scaling = -1)
@threaded for e in eachelement(mesh, dg, cache)
invJ = cache.invJ[1, e]
for i in axes(du, 1)
du[i, e] *= scaling * invJ
end
end
return nothing
end
function invert_jacobian!(du, mesh::DGMultiMesh{NDIMS, <:NonAffine}, equations,
dg::DGMulti, cache; scaling = -1) where {NDIMS}
(; Pq, Vq) = dg.basis
(; invJ) = cache
(; local_values_threaded) = cache.solution_container
@threaded for e in eachelement(mesh, dg, cache)
du_at_quad_points = local_values_threaded[Threads.threadid()]
apply_to_each_field(mul_by!(Vq), du_at_quad_points, view(du, :, e))
for i in eachindex(du_at_quad_points)
du_at_quad_points[i] *= scaling * invJ[i, e]
end
apply_to_each_field(mul_by!(Pq), view(du, :, e), du_at_quad_points)
end
return nothing
end
function calc_sources!(du, u, t, source_terms::Nothing,
mesh, equations, dg::DGMulti, cache)
return nothing
end
function calc_sources!(du, u, t, source_terms::Nothing,
mesh, equations, dg::DGMultiFluxDiffSBP, cache)
return nothing
end
function calc_sources!(du, u, t, source_terms,
mesh, equations, dg::DGMulti, cache)
rd = dg.basis
md = mesh.md
@unpack Pq = rd
(; u_values, local_values_threaded) = cache.solution_container
@threaded for e in eachelement(mesh, dg, cache)
source_values = local_values_threaded[Threads.threadid()]
u_e = view(u_values, :, e)
for i in each_quad_node(mesh, dg, cache)
source_values[i] = source_terms(u_e[i], SVector(getindex.(md.xyzq, i, e)),
t, equations)
end
apply_to_each_field(mul_by_accum!(Pq), view(du, :, e), source_values)
end
return nothing
end
function rhs!(du, u, t, mesh, equations,
boundary_conditions::BC, source_terms::Source,
dg::DGMulti, cache) where {BC, Source}
@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)
@trixi_timeit timer() "volume integral" begin
calc_volume_integral!(du, u, mesh,
have_nonconservative_terms(equations), equations,
dg.volume_integral, dg, cache)
end
@trixi_timeit timer() "prolong2interfaces" begin
prolong2interfaces!(cache, u, mesh, equations, dg)
end
@trixi_timeit timer() "interface flux" begin
calc_interface_flux!(cache, dg.surface_integral, mesh,
have_nonconservative_terms(equations), equations, dg)
end
@trixi_timeit timer() "boundary flux" begin
calc_boundary_flux!(cache, t, boundary_conditions, mesh,
have_nonconservative_terms(equations), equations, dg)
end
@trixi_timeit timer() "surface integral" begin
calc_surface_integral!(du, u, mesh, equations, dg.surface_integral, dg, cache)
end
@trixi_timeit timer() "Jacobian" invert_jacobian!(du, mesh, equations, dg, cache)
@trixi_timeit timer() "source terms" begin
calc_sources!(du, u, t, source_terms, mesh, equations, dg, cache)
end
return nothing
end
end