Path: blob/main/src/solvers/dgsem_tree/dg_1d_parabolic.jl
5591 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: noindent67# This method is called when a `SemidiscretizationHyperbolicParabolic` is constructed.8# It constructs the basic `cache` used throughout the simulation to compute9# the RHS etc.10function create_cache_parabolic(mesh::TreeMesh{1},11equations_hyperbolic::AbstractEquations,12dg::DG, n_elements, uEltype)13parabolic_container = init_parabolic_container_1d(nvariables(equations_hyperbolic),14nnodes(dg), n_elements,15uEltype)1617cache_parabolic = (; parabolic_container)1819return cache_parabolic20end2122# This file collects all methods that have been updated to work with parabolic systems of equations23#24# assumptions: parabolic terms are of the form div(f(u, grad(u))) and25# will be discretized first order form as follows:26# 1. compute grad(u)27# 2. compute f(u, grad(u))28# 3. compute div(f(u, grad(u))) (i.e., the "regular" rhs! call)29# boundary conditions will be applied to both grad(u) and div(f(u, grad(u))).30function rhs_parabolic!(du, u, t, mesh::TreeMesh{1},31equations_parabolic::AbstractEquationsParabolic,32boundary_conditions_parabolic, source_terms_parabolic,33dg::DG, parabolic_scheme, cache, cache_parabolic)34@unpack parabolic_container = cache_parabolic35@unpack u_transformed, gradients, flux_parabolic = parabolic_container3637# Convert conservative variables to a form more suitable for parabolic flux calculations38@trixi_timeit timer() "transform variables" begin39transform_variables!(u_transformed, u, mesh, equations_parabolic, dg,40cache)41end4243# Compute the gradients of the transformed variables44@trixi_timeit timer() "calculate gradient" begin45calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic,46boundary_conditions_parabolic, dg,47parabolic_scheme, cache)48end4950# Compute and store the parabolic fluxes51@trixi_timeit timer() "calculate parabolic fluxes" begin52calc_parabolic_fluxes!(flux_parabolic, gradients, u_transformed, mesh,53equations_parabolic, dg, cache)54end5556# The remainder of this function is essentially a regular rhs! for57# parabolic equations (i.e., it computes the divergence of the parabolic fluxes)58#59# OBS! In `calc_parabolic_fluxes!`, the parabolic flux values at the volume nodes of each element have60# been computed and stored in `flux_parabolic`. In the following, we *reuse* (abuse) the61# `interfaces` and `boundaries` containers in `cache` to interpolate and store the62# *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it63# is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *parabolic flux values*64# and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we65# do not need to recreate the existing data structure only with a different name, and c) we do not66# need to interpolate solutions *and* gradients to the surfaces.6768# Reset du69@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)7071# Calculate volume integral72# This calls the specialized version for the parabolic flux.73@trixi_timeit timer() "volume integral" begin74calc_volume_integral!(du, flux_parabolic, mesh, equations_parabolic, dg, cache)75end7677# Prolong solution to interfaces78# This reuses `prolong2interfaces!` for the purely hyperbolic case.79@trixi_timeit timer() "prolong2interfaces" begin80prolong2interfaces!(cache, flux_parabolic, mesh, equations_parabolic, dg)81end8283# Calculate interface fluxes.84# This calls the specialized version for the parabolic flux.85@trixi_timeit timer() "interface flux" begin86calc_interface_flux!(cache.elements.surface_flux_values,87mesh, equations_parabolic, dg,88parabolic_scheme, cache)89end9091# Prolong solution to boundaries.92# This reuses `prolong2boundaries!` for the purely hyperbolic case.93@trixi_timeit timer() "prolong2boundaries" begin94prolong2boundaries!(cache, flux_parabolic, mesh, equations_parabolic, dg)95end9697# Calculate boundary fluxes.98# This calls the specialized version for parabolic equations.99@trixi_timeit timer() "boundary flux" begin100calc_boundary_flux_divergence!(cache, t,101boundary_conditions_parabolic, mesh,102equations_parabolic,103dg.surface_integral, dg)104end105106# Calculate surface integrals.107# This reuses `calc_surface_integral!` for the purely hyperbolic case.108@trixi_timeit timer() "surface integral" begin109calc_surface_integral!(nothing, du, u, mesh, equations_parabolic,110dg.surface_integral, dg, cache)111end112113# Apply Jacobian from mapping to reference element114@trixi_timeit timer() "Jacobian" begin115apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache)116end117118# Calculate source terms119@trixi_timeit timer() "source terms parabolic" begin120calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,121equations_parabolic, dg, cache)122end123124return nothing125end126127# Transform solution variables prior to taking the gradient128# (e.g., conservative to primitive variables). Defaults to doing nothing.129# TODO: can we avoid copying data?130function transform_variables!(u_transformed, u, mesh::TreeMesh{1},131equations_parabolic::AbstractEquationsParabolic,132dg::DG, cache)133transformation = gradient_variable_transformation(equations_parabolic)134135@threaded for element in eachelement(dg, cache)136# Calculate volume terms in one element137for i in eachnode(dg)138u_node = get_node_vars(u, equations_parabolic, dg, i, element)139u_transformed_node = transformation(u_node, equations_parabolic)140set_node_vars!(u_transformed, u_transformed_node, equations_parabolic, dg,141i, element)142end143end144145return nothing146end147148# This is the version used when calculating the divergence of the parabolic fluxes.149# Identical to weak-form volume integral/kernel for the purely hyperbolic case,150# except that the fluxes are here already precomputed in `calc_parabolic_fluxes!`151function calc_volume_integral!(du, flux_parabolic, mesh::TreeMesh{1},152equations_parabolic::AbstractEquationsParabolic,153dg::DGSEM, cache)154@unpack derivative_hat = dg.basis155156@threaded for element in eachelement(dg, cache)157# Calculate volume terms in one element158for i in eachnode(dg)159flux_1_node = get_node_vars(flux_parabolic, equations_parabolic, dg, i,160element)161162for ii in eachnode(dg)163multiply_add_to_node_vars!(du, derivative_hat[ii, i], flux_1_node,164equations_parabolic, dg, ii, element)165end166end167end168169return nothing170end171172# This is the version used when calculating the divergence of the parabolic fluxes173function calc_interface_flux!(surface_flux_values, mesh::TreeMesh{1},174equations_parabolic, dg::DG, parabolic_scheme,175cache)176@unpack neighbor_ids, orientations = cache.interfaces177178@threaded for interface in eachinterface(dg, cache)179# Get neighboring elements180left_id = neighbor_ids[1, interface]181right_id = neighbor_ids[2, interface]182183# Determine interface direction with respect to elements:184# orientation = 1: left -> 2, right -> 1185left_direction = 2 * orientations[interface]186right_direction = 2 * orientations[interface] - 1187188# Get precomputed fluxes at interfaces189flux_ll, flux_rr = get_surface_node_vars(cache.interfaces.u,190equations_parabolic,191dg, interface)192193# compute interface flux for the DG divergence194flux = flux_parabolic(flux_ll, flux_rr, Divergence(),195equations_parabolic, parabolic_scheme)196197# Copy flux to left and right element storage198for v in eachvariable(equations_parabolic)199surface_flux_values[v, left_direction, left_id] = flux[v]200surface_flux_values[v, right_direction, right_id] = flux[v]201end202end203204return nothing205end206207function calc_parabolic_fluxes!(flux_parabolic, gradients, u_transformed,208mesh::TreeMesh{1},209equations_parabolic::AbstractEquationsParabolic,210dg::DG, cache)211@threaded for element in eachelement(dg, cache)212for i in eachnode(dg)213# Get solution and gradients214u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, element)215gradients_1_node = get_node_vars(gradients, equations_parabolic, dg,216i, element)217218# Calculate parabolic flux and store each component for later use219flux_parabolic_node = flux(u_node, (gradients_1_node,), 1,220equations_parabolic)221set_node_vars!(flux_parabolic, flux_parabolic_node, equations_parabolic, dg,222i, element)223end224end225226return nothing227end228229function calc_boundary_flux_gradient!(cache, t,230boundary_conditions_parabolic::BoundaryConditionPeriodic,231mesh::TreeMesh{1},232equations_parabolic::AbstractEquationsParabolic,233surface_integral, dg::DG)234return nothing235end236237function calc_boundary_flux_divergence!(cache, t,238boundary_conditions_parabolic::BoundaryConditionPeriodic,239mesh::TreeMesh{1},240equations_parabolic::AbstractEquationsParabolic,241surface_integral, dg::DG)242return nothing243end244245function calc_boundary_flux_gradient!(cache, t,246boundary_conditions_parabolic::NamedTuple,247mesh::TreeMesh{1}, # for dispatch only248equations_parabolic::AbstractEquationsParabolic,249surface_integral, dg::DG)250@unpack surface_flux_values = cache.elements251@unpack n_boundaries_per_direction = cache.boundaries252253# Calculate indices254lasts = accumulate(+, n_boundaries_per_direction)255firsts = lasts - n_boundaries_per_direction .+ 1256257# Calc boundary fluxes in each direction258calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,259boundary_conditions_parabolic[1],260equations_parabolic, surface_integral,261dg, cache,2621, firsts[1], lasts[1])263calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,264boundary_conditions_parabolic[2],265equations_parabolic, surface_integral,266dg, cache,2672, firsts[2], lasts[2])268269return nothing270end271272function calc_boundary_flux_by_direction_gradient!(surface_flux_values::AbstractArray{<:Any,2733},274t, boundary_condition,275equations_parabolic::AbstractEquationsParabolic,276surface_integral, dg::DG, cache,277direction, first_boundary,278last_boundary)279@unpack surface_flux = surface_integral280@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries281282@threaded for boundary in first_boundary:last_boundary283# Get neighboring element284neighbor = neighbor_ids[boundary]285286# Get boundary flux287u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, boundary)288if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right289u_inner = u_ll290else # Element is on the right, boundary on the left291u_inner = u_rr292end293294# TODO: revisit if we want more general boundary treatments.295# This assumes the gradient numerical flux at the boundary is the gradient variable,296# which is consistent with BR1, LDG.297flux_inner = u_inner298299x = get_node_coords(node_coordinates, equations_parabolic, dg, boundary)300flux = boundary_condition(flux_inner, u_inner, orientations[boundary],301direction,302x, t, Gradient(), equations_parabolic)303304# Copy flux to left and right element storage305for v in eachvariable(equations_parabolic)306surface_flux_values[v, direction, neighbor] = flux[v]307end308end309310return nothing311end312313function calc_boundary_flux_divergence!(cache, t,314boundary_conditions_parabolic::NamedTuple,315mesh::TreeMesh{1},316equations_parabolic::AbstractEquationsParabolic,317surface_integral, dg::DG)318@unpack surface_flux_values = cache.elements319@unpack n_boundaries_per_direction = cache.boundaries320321# Calculate indices322lasts = accumulate(+, n_boundaries_per_direction)323firsts = lasts - n_boundaries_per_direction .+ 1324325# Calc boundary fluxes in each direction326calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,327boundary_conditions_parabolic[1],328equations_parabolic, surface_integral,329dg, cache,3301, firsts[1], lasts[1])331calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,332boundary_conditions_parabolic[2],333equations_parabolic, surface_integral,334dg, cache,3352, firsts[2], lasts[2])336337return nothing338end339340function calc_boundary_flux_by_direction_divergence!(surface_flux_values::AbstractArray{<:Any,3413},342t,343boundary_condition,344equations_parabolic::AbstractEquationsParabolic,345surface_integral, dg::DG, cache,346direction, first_boundary,347last_boundary)348@unpack surface_flux = surface_integral349350# Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction")351# of the parabolic flux, as computed in `prolong2boundaries!`352@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries353354@threaded for boundary in first_boundary:last_boundary355# Get neighboring element356neighbor = neighbor_ids[boundary]357358# Get parabolic boundary fluxes359flux_ll, flux_rr = get_surface_node_vars(u, equations_parabolic, dg, boundary)360if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right361flux_inner = flux_ll362else # Element is on the right, boundary on the left363flux_inner = flux_rr364end365366x = get_node_coords(node_coordinates, equations_parabolic, dg, boundary)367368# TODO: add a field in `cache.boundaries` for gradient information.369# Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information.370# This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion2D and371# NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion2D as of 2022-6-27.372# It will not work with implementations which utilize `u_inner` to impose boundary conditions.373flux = boundary_condition(flux_inner, nothing, orientations[boundary],374direction,375x, t, Divergence(), equations_parabolic)376377# Copy flux to left and right element storage378for v in eachvariable(equations_parabolic)379surface_flux_values[v, direction, neighbor] = flux[v]380end381end382383return nothing384end385386function calc_volume_integral_gradient!(gradients, u_transformed,387mesh::TreeMesh{1}, # for dispatch only388equations_parabolic::AbstractEquationsParabolic,389dg::DGSEM, cache)390@unpack derivative_hat = dg.basis391392@threaded for element in eachelement(dg, cache)393# Calculate volume terms in one element,394# corresponds to `kernel` functions for the hyperbolic part of the flux395for i in eachnode(dg)396u_node = get_node_vars(u_transformed, equations_parabolic, dg,397i, element)398399for ii in eachnode(dg)400multiply_add_to_node_vars!(gradients, derivative_hat[ii, i],401u_node, equations_parabolic, dg,402ii, element)403end404end405end406407return nothing408end409410function calc_interface_flux_gradient!(surface_flux_values,411mesh::TreeMesh{1},412equations_parabolic,413dg::DG, parabolic_scheme, cache)414@unpack neighbor_ids, orientations = cache.interfaces415416@threaded for interface in eachinterface(dg, cache)417# Get neighboring elements418left_id = neighbor_ids[1, interface]419right_id = neighbor_ids[2, interface]420421# Determine interface direction with respect to elements:422# orientation = 1: left -> 2, right -> 1423left_direction = 2 * orientations[interface]424right_direction = 2 * orientations[interface] - 1425426# Call pointwise Riemann solver427u_ll, u_rr = get_surface_node_vars(cache.interfaces.u,428equations_parabolic, dg, interface)429430flux = flux_parabolic(u_ll, u_rr, Gradient(),431equations_parabolic, parabolic_scheme)432433# Copy flux to left and right element storage434for v in eachvariable(equations_parabolic)435surface_flux_values[v, left_direction, left_id] = flux[v]436# No sign flip needed for gradient computation because for parabolic terms,437# the normals are not embedded in `flux_` for gradient computations.438surface_flux_values[v, right_direction, right_id] = flux[v]439end440end441442return nothing443end444445function calc_surface_integral_gradient!(gradients,446mesh::TreeMesh{1}, # for dispatch only447equations_parabolic::AbstractEquationsParabolic,448dg::DGSEM, cache)449@unpack inverse_weights = dg.basis450@unpack surface_flux_values = cache.elements451452# Note that all fluxes have been computed with outward-pointing normal vectors.453# We also use explicit assignments instead of `+=` to let `@muladd` turn these454# into FMAs (see comment at the top of the file).455factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±1456@threaded for element in eachelement(dg, cache)457for v in eachvariable(equations_parabolic)458# surface at -x459gradients[v, 1, element] = (gradients[v, 1, element] -460surface_flux_values[v, 1, element] *461factor)462463# surface at +x464gradients[v, nnodes(dg), element] = (gradients[v, nnodes(dg), element] +465surface_flux_values[v, 2, element] *466factor)467end468end469470return nothing471end472473function calc_surface_integral_gradient!(gradients,474mesh::TreeMesh{1}, # for dispatch only475equations_parabolic::AbstractEquationsParabolic,476dg::DGSEM{<:GaussLegendreBasis}, cache)477@unpack boundary_interpolation_inverse_weights = dg.basis478@unpack surface_flux_values = cache.elements479480# Note that all fluxes have been computed with outward-pointing normal vectors.481# We also use explicit assignments instead of `+=` to let `@muladd` turn these482# into FMAs (see comment at the top of the file).483@threaded for element in eachelement(dg, cache)484for v in eachvariable(equations_parabolic)485# Aliases for repeatedly accessed variables486surface_flux_minus = surface_flux_values[v, 1, element]487surface_flux_plus = surface_flux_values[v, 2, element]488for ii in eachnode(dg)489# surface at -x490gradients[v, ii, element] = (gradients[v, ii, element] -491surface_flux_minus *492boundary_interpolation_inverse_weights[ii,4931])494495# surface at +x496gradients[v, ii, element] = (gradients[v, ii, element] +497surface_flux_plus *498boundary_interpolation_inverse_weights[ii,4992])500end501end502end503504return nothing505end506507# Calculate the gradient of the transformed variables508function calc_gradient!(gradients, u_transformed, t, mesh::TreeMesh{1},509equations_parabolic, boundary_conditions_parabolic,510dg::DG, parabolic_scheme, cache)511512# Reset gradients513@trixi_timeit timer() "reset gradients" begin514set_zero!(gradients, dg, cache)515end516517# Calculate volume integral518@trixi_timeit timer() "volume integral" begin519calc_volume_integral_gradient!(gradients, u_transformed,520mesh, equations_parabolic, dg, cache)521end522523# Prolong solution to interfaces.524# This reusues `prolong2interfaces!` for the purely hyperbolic case.525@trixi_timeit timer() "prolong2interfaces" begin526prolong2interfaces!(cache, u_transformed, mesh,527equations_parabolic, dg)528end529530# Calculate interface fluxes531@trixi_timeit timer() "interface flux" begin532@unpack surface_flux_values = cache.elements533calc_interface_flux_gradient!(surface_flux_values, mesh, equations_parabolic,534dg, parabolic_scheme, cache)535end536537# Prolong solution to boundaries.538# This reuses `prolong2boundaries!` for the purely hyperbolic case.539@trixi_timeit timer() "prolong2boundaries" begin540prolong2boundaries!(cache, u_transformed, mesh, equations_parabolic, dg)541end542543# Calculate boundary fluxes544@trixi_timeit timer() "boundary flux" begin545calc_boundary_flux_gradient!(cache, t,546boundary_conditions_parabolic, mesh,547equations_parabolic,548dg.surface_integral, dg)549end550551# Calculate surface integrals552@trixi_timeit timer() "surface integral" begin553calc_surface_integral_gradient!(gradients, mesh, equations_parabolic,554dg, cache)555end556557# Apply Jacobian from mapping to reference element558@trixi_timeit timer() "Jacobian" begin559apply_jacobian_parabolic!(gradients, mesh, equations_parabolic, dg,560cache)561end562563return nothing564end565566# Needed to *not* flip the sign of the inverse Jacobian.567# This is because the parabolic fluxes are assumed to be of the form568# `du/dt + df/dx = dg/dx + source(x,t)`,569# where f(u) is the inviscid flux and g(u) is the parabolic flux.570function apply_jacobian_parabolic!(du::AbstractArray, mesh::TreeMesh{1},571equations_parabolic::AbstractEquationsParabolic,572dg::DG, cache)573@unpack inverse_jacobian = cache.elements574575@threaded for element in eachelement(dg, cache)576factor = inverse_jacobian[element]577578for i in eachnode(dg)579for v in eachvariable(equations_parabolic)580du[v, i, element] *= factor581end582end583end584585return nothing586end587588# Need dimension specific version to avoid error at dispatching589function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic::Nothing,590equations_parabolic::AbstractEquations{1}, dg::DG,591cache)592return nothing593end594595function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,596equations_parabolic::AbstractEquations{1}, dg::DG,597cache)598@unpack node_coordinates = cache.elements599600@threaded for element in eachelement(dg, cache)601for i in eachnode(dg)602u_local = get_node_vars(u, equations_parabolic, dg, i, element)603gradients_x_local = get_node_vars(gradients, equations_parabolic, dg, i,604element)605x_local = get_node_coords(node_coordinates, equations_parabolic, dg,606i, element)607du_local = source_terms_parabolic(u_local, (gradients_x_local,), x_local, t,608equations_parabolic)609add_to_node_vars!(du, du_local, equations_parabolic, dg, i, element)610end611end612613return nothing614end615end # @muladd616617618