Path: blob/main/src/solvers/dgmulti/dg_parabolic.jl
5591 views
# version for standard (e.g., non-entropy stable or flux differencing) schemes1function create_cache_parabolic(mesh::DGMultiMesh,2equations_hyperbolic::AbstractEquations,3dg::DGMulti, n_elements, uEltype)45# default to taking derivatives of all hyperbolic variables6# TODO: parabolic; utilize the parabolic variables in `equations_parabolic` to reduce memory usage in the parabolic cache7nvars = nvariables(equations_hyperbolic)89(; M, Vq, Pq, Drst) = dg.basis1011# gradient operators: map from nodes to quadrature12strong_differentiation_matrices = map(A -> Vq * A, Drst)13gradient_lift_matrix = Vq * dg.basis.LIFT1415# divergence operators: map from quadrature to nodes16weak_differentiation_matrices = map(A -> (M \ (-A' * M * Pq)), Drst)17divergence_lift_matrix = dg.basis.LIFT18projection_face_interpolation_matrix = dg.basis.Vf * dg.basis.Pq1920# evaluate geometric terms at quadrature points in case the mesh is curved21(; md) = mesh22J = dg.basis.Vq * md.J23invJ = inv.(J)24dxidxhatj = map(x -> dg.basis.Vq * x, md.rstxyzJ)2526# u_transformed stores "transformed" variables for computing the gradient27u_transformed = allocate_nested_array(uEltype, nvars, size(md.x), dg)28gradients = SVector{ndims(mesh)}(ntuple(_ -> similar(u_transformed,29(dg.basis.Nq,30mesh.md.num_elements)),31ndims(mesh)))32flux_parabolic = similar.(gradients)3334u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg)35scalar_flux_face_values = similar(u_face_values)36gradients_face_values = ntuple(_ -> similar(u_face_values), ndims(mesh))3738local_u_values_threaded = [similar(u_transformed, dg.basis.Nq)39for _ in 1:Threads.maxthreadid()]40local_flux_parabolic_threaded = [SVector{ndims(mesh)}(ntuple(_ -> similar(u_transformed,41dg.basis.Nq),42ndims(mesh)))43for _ in 1:Threads.maxthreadid()]44local_flux_face_values_threaded = [similar(scalar_flux_face_values[:, 1])45for _ in 1:Threads.maxthreadid()]4647return (; u_transformed, gradients, flux_parabolic,48weak_differentiation_matrices, strong_differentiation_matrices,49gradient_lift_matrix, projection_face_interpolation_matrix,50divergence_lift_matrix,51dxidxhatj, J, invJ, # geometric terms52u_face_values, gradients_face_values, scalar_flux_face_values,53local_u_values_threaded, local_flux_parabolic_threaded,54local_flux_face_values_threaded)55end5657# Transform solution variables prior to taking the gradient58# (e.g., conservative to primitive variables). Defaults to doing nothing.59# TODO: can we avoid copying data?60function transform_variables!(u_transformed, u, mesh,61equations_parabolic::AbstractEquationsParabolic,62dg::DGMulti, cache)63transformation = gradient_variable_transformation(equations_parabolic)6465@threaded for i in eachindex(u)66u_transformed[i] = transformation(u[i], equations_parabolic)67end6869return nothing70end7172# TODO: reuse entropy projection computations for DGMultiFluxDiff{<:Polynomial} (including `GaussSBP` solvers)73function calc_surface_integral_gradient!(gradients, u, scalar_flux_face_values,74mesh, equations::AbstractEquationsParabolic,75dg::DGMulti, cache, cache_parabolic)76(; gradient_lift_matrix, local_flux_face_values_threaded) = cache_parabolic77@threaded for e in eachelement(mesh, dg)78local_flux_values = local_flux_face_values_threaded[Threads.threadid()]79for dim in eachdim(mesh)80for i in eachindex(local_flux_values)81# compute flux * (nx, ny, nz)82local_flux_values[i] = scalar_flux_face_values[i, e] *83mesh.md.nxyzJ[dim][i, e]84end85apply_to_each_field(mul_by_accum!(gradient_lift_matrix),86view(gradients[dim], :, e), local_flux_values)87end88end8990return nothing91end9293function calc_volume_integral_gradient!(gradients, u, mesh::DGMultiMesh,94equations::AbstractEquationsParabolic,95dg::DGMulti, cache, cache_parabolic)96(; strong_differentiation_matrices) = cache_parabolic9798# compute volume contributions to gradients99@threaded for e in eachelement(mesh, dg)100for i in eachdim(mesh), j in eachdim(mesh)101102# We assume each element is affine (e.g., constant geometric terms) here.103dxidxhatj = mesh.md.rstxyzJ[i, j][1, e]104105apply_to_each_field(mul_by_accum!(strong_differentiation_matrices[j],106dxidxhatj),107view(gradients[i], :, e), view(u, :, e))108end109end110111return nothing112end113114function calc_volume_integral_gradient!(gradients, u, mesh::DGMultiMesh{NDIMS, <:NonAffine},115equations::AbstractEquationsParabolic,116dg::DGMulti, cache, cache_parabolic) where {NDIMS}117(; strong_differentiation_matrices, dxidxhatj, local_flux_parabolic_threaded) = cache_parabolic118119# compute volume contributions to gradients120@threaded for e in eachelement(mesh, dg)121122# compute gradients with respect to reference coordinates123local_reference_gradients = local_flux_parabolic_threaded[Threads.threadid()]124for i in eachdim(mesh)125apply_to_each_field(mul_by!(strong_differentiation_matrices[i]),126local_reference_gradients[i], view(u, :, e))127end128129# rotate to physical frame on each element130for i in eachdim(mesh), j in eachdim(mesh)131for node in eachindex(local_reference_gradients[j])132gradients[i][node, e] = gradients[i][node, e] +133dxidxhatj[i, j][node, e] *134local_reference_gradients[j][node]135end136end137end138139return nothing140end141142function calc_interface_flux_gradient!(scalar_flux_face_values,143mesh::DGMultiMesh, equations,144dg::DGMulti,145parabolic_scheme::ParabolicFormulationBassiRebay1,146cache, cache_parabolic)147(; u_face_values) = cache_parabolic148(; mapM, mapP) = mesh.md149@threaded for face_node_index in each_face_node_global(mesh, dg)150idM, idP = mapM[face_node_index], mapP[face_node_index]151uM = u_face_values[idM]152uP = u_face_values[idP]153# Here, we use the "strong" formulation to compute the gradient.154# This guarantees that the parabolic formulation is symmetric and155# stable on curved meshes with variable geometric terms.156scalar_flux_face_values[idM] = 0.5f0 * (uP - uM)157end158159return nothing160end161162function calc_gradient!(gradients, u::StructArray, t, mesh::DGMultiMesh,163equations::AbstractEquationsParabolic,164boundary_conditions, dg::DGMulti, parabolic_scheme,165cache, cache_parabolic)166for dim in eachindex(gradients)167set_zero!(gradients[dim], dg)168end169170calc_volume_integral_gradient!(gradients, u, mesh, equations, dg, cache,171cache_parabolic)172173# prolong to interfaces174(; u_face_values) = cache_parabolic175apply_to_each_field(mul_by!(dg.basis.Vf), u_face_values, u)176177# compute fluxes at interfaces178(; scalar_flux_face_values) = cache_parabolic179calc_interface_flux_gradient!(scalar_flux_face_values,180mesh, equations, dg, parabolic_scheme, cache,181cache_parabolic)182183calc_boundary_flux!(scalar_flux_face_values, u_face_values, t, Gradient(),184boundary_conditions,185mesh, equations, dg, cache, cache_parabolic)186187# compute surface contributions188calc_surface_integral_gradient!(gradients, u, scalar_flux_face_values,189mesh, equations, dg, cache, cache_parabolic)190191invert_jacobian_gradient!(gradients, mesh, equations, dg, cache, cache_parabolic)192193return nothing194end195196# affine mesh - constant Jacobian version197function invert_jacobian_gradient!(gradients, mesh::DGMultiMesh, equations, dg::DGMulti,198cache, cache_parabolic)199@threaded for e in eachelement(mesh, dg)200201# Here, we exploit the fact that J is constant on affine elements,202# so we only have to access invJ once per element.203invJ = cache_parabolic.invJ[1, e]204205for dim in eachdim(mesh)206for i in axes(gradients[dim], 1)207gradients[dim][i, e] = gradients[dim][i, e] * invJ208end209end210end211212return nothing213end214215# non-affine mesh - variable Jacobian version216function invert_jacobian_gradient!(gradients, mesh::DGMultiMesh{NDIMS, <:NonAffine},217equations,218dg::DGMulti, cache, cache_parabolic) where {NDIMS}219(; invJ) = cache_parabolic220@threaded for e in eachelement(mesh, dg)221for dim in eachdim(mesh)222for i in axes(gradients[dim], 1)223gradients[dim][i, e] = gradients[dim][i, e] * invJ[i, e]224end225end226end227228return nothing229end230231# do nothing for periodic domains232function calc_boundary_flux!(flux, u, t, operator_type, ::BoundaryConditionPeriodic,233mesh, equations::AbstractEquationsParabolic, dg::DGMulti,234cache, cache_parabolic)235return nothing236end237238# "lispy tuple programming" instead of for loop for type stability239function calc_boundary_flux!(flux, u, t, operator_type, boundary_conditions,240mesh, equations, dg::DGMulti, cache, cache_parabolic)241242# peel off first boundary condition243calc_single_boundary_flux!(flux, u, t, operator_type, first(boundary_conditions),244first(keys(boundary_conditions)),245mesh, equations, dg, cache, cache_parabolic)246247# recurse on the remainder of the boundary conditions248calc_boundary_flux!(flux, u, t, operator_type, Base.tail(boundary_conditions),249mesh, equations, dg, cache, cache_parabolic)250251return nothing252end253254# terminate recursion255function calc_boundary_flux!(flux, u, t, operator_type,256boundary_conditions::NamedTuple{(), Tuple{}},257mesh, equations, dg::DGMulti, cache, cache_parabolic)258return nothing259end260261function calc_single_boundary_flux!(flux_face_values, u_face_values, t,262operator_type, boundary_condition, boundary_key,263mesh, equations, dg::DGMulti{NDIMS}, cache,264cache_parabolic) where {NDIMS}265rd = dg.basis266md = mesh.md267268num_faces = StartUpDG.num_faces(rd.element_type)269num_pts_per_face = rd.Nfq ÷ num_faces270(; xyzf, nxyz) = md271for f in mesh.boundary_faces[boundary_key]272for i in Base.OneTo(num_pts_per_face)273274# reverse engineer element + face node indices (avoids reshaping arrays)275e = ((f - 1) ÷ num_faces) + 1276fid = i + ((f - 1) % num_faces) * num_pts_per_face277278face_normal = SVector{NDIMS}(getindex.(nxyz, fid, e))279face_coordinates = SVector{NDIMS}(getindex.(xyzf, fid, e))280281# for both the gradient and the divergence, the boundary flux is scalar valued.282# for the gradient, it is the solution; for divergence, it is the normal flux.283flux_face_values[fid, e] = boundary_condition(flux_face_values[fid, e],284u_face_values[fid, e],285face_normal, face_coordinates, t,286operator_type, equations)287288# Here, we use the "strong form" for the Gradient (and the "weak form" for Divergence).289# `flux_face_values` should contain the boundary values for `u`, and we290# subtract off `u_face_values[fid, e]` because we are using the strong formulation to291# compute the gradient.292if operator_type isa Gradient293flux_face_values[fid, e] = flux_face_values[fid, e] - u_face_values[fid, e]294end295end296end297return nothing298end299300function calc_parabolic_fluxes!(flux_parabolic, u, gradients, mesh::DGMultiMesh,301equations::AbstractEquationsParabolic,302dg::DGMulti, cache, cache_parabolic)303for dim in eachdim(mesh)304set_zero!(flux_parabolic[dim], dg)305end306307(; local_u_values_threaded) = cache_parabolic308309@threaded for e in eachelement(mesh, dg)310311# reset local storage for each element, interpolate u to quadrature points312# TODO: DGMulti. Specialize for nodal collocation methods (SBP, GaussSBP)?313local_u_values = local_u_values_threaded[Threads.threadid()]314fill!(local_u_values, zero(eltype(local_u_values)))315apply_to_each_field(mul_by!(dg.basis.Vq), local_u_values, view(u, :, e))316317# compute parabolic flux at quad points318for i in eachindex(local_u_values)319u_i = local_u_values[i]320gradients_i = getindex.(gradients, i, e)321for dim in eachdim(mesh)322flux_parabolic_i = flux(u_i, gradients_i, dim, equations)323setindex!(flux_parabolic[dim], flux_parabolic_i, i, e)324end325end326end327328return nothing329end330331# no penalization for a BR1 parabolic solver332function calc_parabolic_penalty!(scalar_flux_face_values, u_face_values, t,333boundary_conditions,334mesh, equations::AbstractEquationsParabolic,335dg::DGMulti,336parabolic_scheme::ParabolicFormulationBassiRebay1,337cache, cache_parabolic)338return nothing339end340341function calc_parabolic_penalty!(scalar_flux_face_values, u_face_values, t,342boundary_conditions, mesh,343equations::AbstractEquationsParabolic,344dg::DGMulti, parabolic_scheme, cache, cache_parabolic)345# compute fluxes at interfaces346(; scalar_flux_face_values) = cache_parabolic347(; mapM, mapP) = mesh.md348@threaded for face_node_index in each_face_node_global(mesh, dg)349idM, idP = mapM[face_node_index], mapP[face_node_index]350uM, uP = u_face_values[idM], u_face_values[idP]351scalar_flux_face_values[idM] = scalar_flux_face_values[idM] +352penalty(uP, uM, equations, parabolic_scheme)353end354return nothing355end356357function calc_volume_integral_divergence!(du, u, flux_parabolic, mesh::DGMultiMesh,358equations::AbstractEquationsParabolic,359dg::DGMulti, cache, cache_parabolic)360(; weak_differentiation_matrices) = cache_parabolic361362# compute volume contributions to divergence363@threaded for e in eachelement(mesh, dg)364for i in eachdim(mesh), j in eachdim(mesh)365dxidxhatj = mesh.md.rstxyzJ[i, j][1, e] # assumes mesh is affine366apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j], dxidxhatj),367view(du, :, e), view(flux_parabolic[i], :, e))368end369end370371return nothing372end373374function calc_volume_integral_divergence!(du, u, flux_parabolic,375mesh::DGMultiMesh{NDIMS, <:NonAffine},376equations::AbstractEquationsParabolic,377dg::DGMulti, cache, cache_parabolic) where {NDIMS}378(; weak_differentiation_matrices, dxidxhatj, local_flux_parabolic_threaded) = cache_parabolic379380# compute volume contributions to divergence381@threaded for e in eachelement(mesh, dg)382local_parabolic_flux = local_flux_parabolic_threaded[Threads.threadid()][1]383for i in eachdim(mesh)384# rotate flux to reference coordinates385fill!(local_parabolic_flux, zero(eltype(local_parabolic_flux)))386for j in eachdim(mesh)387for node in eachindex(local_parabolic_flux)388local_parabolic_flux[node] = local_parabolic_flux[node] +389dxidxhatj[j, i][node, e] *390flux_parabolic[j][node, e]391end392end393394# differentiate with respect to reference coordinates395apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[i]),396view(du, :, e), local_parabolic_flux)397end398end399400return nothing401end402403function calc_interface_flux_divergence!(scalar_flux_face_values,404mesh, equations, dg,405parabolic_scheme::ParabolicFormulationBassiRebay1,406cache, cache_parabolic)407flux_parabolic_face_values = cache_parabolic.gradients_face_values # reuse storage408(; mapM, mapP, nxyzJ) = mesh.md409410@threaded for face_node_index in each_face_node_global(mesh, dg, cache, cache_parabolic)411idM, idP = mapM[face_node_index], mapP[face_node_index]412413# compute f(u, ∇u) ⋅ n414flux_face_value = zero(eltype(scalar_flux_face_values))415for dim in eachdim(mesh)416fM = flux_parabolic_face_values[dim][idM]417fP = flux_parabolic_face_values[dim][idP]418# Here, we use the "weak" formulation to compute the divergence (to ensure stability on curved meshes).419flux_face_value = flux_face_value +4200.5f0 * (fP + fM) * nxyzJ[dim][face_node_index]421end422scalar_flux_face_values[idM] = flux_face_value423end424425return nothing426end427428function calc_divergence!(du, u::StructArray, t, flux_parabolic, mesh::DGMultiMesh,429equations::AbstractEquationsParabolic,430boundary_conditions, dg::DGMulti, parabolic_scheme, cache,431cache_parabolic)432set_zero!(du, dg)433434calc_volume_integral_divergence!(du, u, flux_parabolic, mesh, equations, dg, cache,435cache_parabolic)436437# interpolates from solution coefficients to face quadrature points438(; projection_face_interpolation_matrix) = cache_parabolic439flux_parabolic_face_values = cache_parabolic.gradients_face_values # reuse storage440for dim in eachdim(mesh)441apply_to_each_field(mul_by!(projection_face_interpolation_matrix),442flux_parabolic_face_values[dim], flux_parabolic[dim])443end444445# compute fluxes at interfaces446(; scalar_flux_face_values) = cache_parabolic447calc_interface_flux_divergence!(scalar_flux_face_values,448mesh, equations, dg, parabolic_scheme, cache,449cache_parabolic)450451calc_boundary_flux!(scalar_flux_face_values, cache_parabolic.u_face_values, t,452Divergence(),453boundary_conditions, mesh, equations, dg, cache, cache_parabolic)454455calc_parabolic_penalty!(scalar_flux_face_values, cache_parabolic.u_face_values, t,456boundary_conditions, mesh, equations, dg, parabolic_scheme,457cache, cache_parabolic)458459# surface contributions460apply_to_each_field(mul_by_accum!(cache_parabolic.divergence_lift_matrix), du,461scalar_flux_face_values)462463return nothing464end465466# assumptions: parabolic terms are of the form div(f(u, grad(u))) and467# will be discretized first order form as follows:468# 1. compute grad(u)469# 2. compute f(u, grad(u))470# 3. compute div(u)471# boundary conditions will be applied to both grad(u) and div(u).472function rhs_parabolic!(du, u, t, mesh::DGMultiMesh,473equations_parabolic::AbstractEquationsParabolic,474boundary_conditions, source_terms_parabolic,475dg::DGMulti, parabolic_scheme, cache, cache_parabolic)476set_zero!(du, dg)477478@trixi_timeit timer() "transform variables" begin479(; u_transformed, gradients, flux_parabolic) = cache_parabolic480transform_variables!(u_transformed, u, mesh, equations_parabolic,481dg, cache)482end483484@trixi_timeit timer() "calc gradient" begin485calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic,486boundary_conditions, dg, parabolic_scheme, cache, cache_parabolic)487end488489@trixi_timeit timer() "calc parabolic fluxes" begin490calc_parabolic_fluxes!(flux_parabolic, u_transformed, gradients,491mesh, equations_parabolic, dg, cache, cache_parabolic)492end493494@trixi_timeit timer() "calc divergence" begin495calc_divergence!(du, u_transformed, t, flux_parabolic, mesh, equations_parabolic,496boundary_conditions, dg, parabolic_scheme, cache, cache_parabolic)497end498499@trixi_timeit timer() "jacobian" begin500# Note: we do not flip the sign of the geometric Jacobian here.501# This is because the parabolic fluxes are assumed to be of the form502# `du/dt + df/dx = dg/dx + source(x,t)`,503# where f(u) is the inviscid flux and g(u) is the parabolic flux.504invert_jacobian!(du, mesh, equations_parabolic, dg, cache; scaling = 1)505end506507@trixi_timeit timer() "source terms parabolic" begin508calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic, mesh,509equations_parabolic, dg, cache)510end511512return nothing513end514515# Multiple calc_sources_parabolic! to resolve method ambiguities516function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic::Nothing,517mesh, equations_parabolic,518dg::Union{<:DGMulti, <:DGMultiFluxDiffSBP}, cache)519return nothing520end521522# uses quadrature + projection to compute source terms.523function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,524mesh, equations_parabolic, dg::DGMulti, cache)525md = mesh.md526@threaded for e in eachelement(mesh, dg, cache)527for i in eachnode(dg)528du[i, e] = du[i, e] +529source_terms_parabolic(u[i, e], SVector(getindex.(gradients, i, e)),530SVector(getindex.(md.xyzq, i, e)),531t, equations_parabolic)532end533end534535return nothing536end537538539