Path: blob/main/src/callbacks_step/amr_dg2d.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: noindent67# Redistribute data for load balancing after partitioning the mesh8function rebalance_solver!(u_ode::AbstractVector, mesh::TreeMesh{2}, equations,9dg::DGSEM, cache, old_mpi_ranks_per_cell)10if cache.elements.cell_ids == local_leaf_cells(mesh.tree)11# Cell ids of the current elements are the same as the local leaf cells of the12# newly partitioned mesh, so the solver doesn't need to be rebalanced on this rank.13# MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do14# locally (there are other MPI ranks that need to be rebalanced if this function is called)15reinitialize_containers!(mesh, equations, dg, cache)16return17end1819# Retain current solution data20old_n_elements = nelements(dg, cache)21old_cell_ids = copy(cache.elements.cell_ids)22old_u_ode = copy(u_ode)23GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed24# Use `wrap_array_native` instead of `wrap_array` since MPI might not interact25# nicely with non-base array types26old_u = wrap_array_native(old_u_ode, mesh, equations, dg, cache)2728@trixi_timeit timer() "reinitialize data structures" begin29reinitialize_containers!(mesh, equations, dg, cache)30end3132resize!(u_ode,33nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache))34u = wrap_array_native(u_ode, mesh, equations, dg, cache)3536# Get new list of leaf cells37leaf_cell_ids = local_leaf_cells(mesh.tree)3839@trixi_timeit timer() "exchange data" begin40# Collect MPI requests for MPI_Waitall41requests = Vector{MPI.Request}()4243# Find elements that will change their rank and send their data to the new rank44for old_element_id in 1:old_n_elements45cell_id = old_cell_ids[old_element_id]46if !(cell_id in leaf_cell_ids)47# Send element data to new rank, use cell_id as tag (non-blocking)48dest = mesh.tree.mpi_ranks[cell_id]49request = MPI.Isend(@view(old_u[:, .., old_element_id]), dest,50cell_id, mpi_comm())51push!(requests, request)52end53end5455# Loop over all elements in new container and either copy them from old container56# or receive them with MPI57for element in eachelement(dg, cache)58cell_id = cache.elements.cell_ids[element]59if cell_id in old_cell_ids60old_element_id = searchsortedfirst(old_cell_ids, cell_id)61# Copy old element data to new element container62@views u[:, .., element] .= old_u[:, .., old_element_id]63else64# Receive old element data65src = old_mpi_ranks_per_cell[cell_id]66request = MPI.Irecv!(@view(u[:, .., element]), src, cell_id,67mpi_comm())68push!(requests, request)69end70end7172# Wait for all non-blocking MPI send/receive operations to finish73MPI.Waitall(requests, MPI.Status)74end75end # GC.@preserve old_u_ode7677return nothing78end7980# Refine elements in the DG solver based on a list of cell_ids that should be refined.81# On `P4estMesh`, if an element refines the solution scaled by the Jacobian `J*u` is interpolated82# from the parent element into the four children elements. The solution on each child83# element is then recovered by dividing by the new element Jacobians.84function refine!(u_ode::AbstractVector, adaptor, mesh::Union{TreeMesh{2}, P4estMesh{2}},85equations, dg::DGSEM, cache, elements_to_refine)86# Return early if there is nothing to do87if isempty(elements_to_refine)88if mpi_isparallel()89# MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do90# locally (there still might be other MPI ranks that have refined elements)91reinitialize_containers!(mesh, equations, dg, cache)92end93return94end9596# Determine for each existing element whether it needs to be refined97needs_refinement = falses(nelements(dg, cache))98needs_refinement[elements_to_refine] .= true99100# Retain current solution data101old_n_elements = nelements(dg, cache)102old_u_ode = copy(u_ode)103old_inverse_jacobian = copy(cache.elements.inverse_jacobian)104# OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed105GC.@preserve old_u_ode old_inverse_jacobian begin106old_u = wrap_array(old_u_ode, mesh, equations, dg, cache)107108if mesh isa P4estMesh109# Loop over all elements in old container and scale the old solution by the Jacobian110# prior to projection111for old_element_id in 1:old_n_elements112for v in eachvariable(equations)113old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./114old_inverse_jacobian[..,115old_element_id])116end117end118end119120@trixi_timeit timer() "reinitialize data structures" begin121reinitialize_containers!(mesh, equations, dg, cache)122end123124resize!(u_ode,125nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache))126u = wrap_array(u_ode, mesh, equations, dg, cache)127128# Loop over all elements in old container and either copy them or refine them129element_id = 1130for old_element_id in 1:old_n_elements131if needs_refinement[old_element_id]132# Refine element and store solution directly in new data structure133refine_element!(u, element_id, old_u, old_element_id,134adaptor, equations, dg)135136if mesh isa P4estMesh137# Before `element_id` is incremented, divide by the new Jacobians on each138# child element and save the result139for m in 0:3 # loop over the children140for v in eachvariable(equations)141u[v, .., element_id + m] .*= (0.25f0 .*142cache.elements.inverse_jacobian[..,143element_id + m])144end145end146end147148# Increment `element_id` on the refined mesh with the number149# of children, i.e., 4 in 2D150element_id += 2^ndims(mesh)151else152if mesh isa P4estMesh153# Copy old element data to new element container and remove Jacobian scaling154for v in eachvariable(equations)155u[v, .., element_id] .= (old_u[v, .., old_element_id] .*156old_inverse_jacobian[..,157old_element_id])158end159else # isa TreeMesh160@views u[:, .., element_id] .= old_u[:, .., old_element_id]161end162# No refinement occurred, so increment `element_id` on the new mesh by one163element_id += 1164end165end166# If everything is correct, we should have processed all elements.167# Depending on whether the last element processed above had to be refined or not,168# the counter `element_id` can have two different values at the end.169@assert element_id ==170nelements(dg, cache) +1711||element_id == nelements(dg, cache) + 2^ndims(mesh) "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))"172end # GC.@preserve old_u_ode old_inverse_jacobian173174# Sanity check175if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 &&176!mpi_isparallel()177@assert ninterfaces(cache.interfaces)==ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements")178end179180return nothing181end182183function refine!(u_ode::AbstractVector, adaptor,184mesh::Union{TreeMesh{2}, P4estMesh{2}, TreeMesh{3}, P4estMesh{3}},185equations, dg::DGSEM, cache, cache_parabolic,186elements_to_refine)187# Call `refine!` for the hyperbolic part, which does the heavy lifting of188# actually transferring the solution to the refined cells189refine!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_refine)190191# Resize parabolic helper variables192@unpack parabolic_container = cache_parabolic193resize!(parabolic_container, equations, dg, cache)194195return nothing196end197198# TODO: Taal compare performance of different implementations199# Refine solution data u for an element, using L2 projection (interpolation)200function refine_element!(u::AbstractArray{<:Any, 4}, element_id,201old_u, old_element_id,202adaptor::LobattoLegendreAdaptorL2, equations, dg)203@unpack forward_upper, forward_lower = adaptor204205# Store new element ids206lower_left_id = element_id207lower_right_id = element_id + 1208upper_left_id = element_id + 2209upper_right_id = element_id + 3210211@boundscheck begin212@assert old_element_id >= 1213@assert size(old_u, 1) == nvariables(equations)214@assert size(old_u, 2) == nnodes(dg)215@assert size(old_u, 3) == nnodes(dg)216@assert size(old_u, 4) >= old_element_id217@assert element_id >= 1218@assert size(u, 1) == nvariables(equations)219@assert size(u, 2) == nnodes(dg)220@assert size(u, 3) == nnodes(dg)221@assert size(u, 4) >= element_id + 3222end223224# Interpolate to lower left element225for j in eachnode(dg), i in eachnode(dg)226acc = zero(get_node_vars(u, equations, dg, i, j, element_id))227for l in eachnode(dg), k in eachnode(dg)228acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) *229forward_lower[i, k] * forward_lower[j, l]230end231set_node_vars!(u, acc, equations, dg, i, j, lower_left_id)232end233234# Interpolate to lower right element235for j in eachnode(dg), i in eachnode(dg)236acc = zero(get_node_vars(u, equations, dg, i, j, element_id))237for l in eachnode(dg), k in eachnode(dg)238acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) *239forward_upper[i, k] * forward_lower[j, l]240end241set_node_vars!(u, acc, equations, dg, i, j, lower_right_id)242end243244# Interpolate to upper left element245for j in eachnode(dg), i in eachnode(dg)246acc = zero(get_node_vars(u, equations, dg, i, j, element_id))247for l in eachnode(dg), k in eachnode(dg)248acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) *249forward_lower[i, k] * forward_upper[j, l]250end251set_node_vars!(u, acc, equations, dg, i, j, upper_left_id)252end253254# Interpolate to upper right element255for j in eachnode(dg), i in eachnode(dg)256acc = zero(get_node_vars(u, equations, dg, i, j, element_id))257for l in eachnode(dg), k in eachnode(dg)258acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) *259forward_upper[i, k] * forward_upper[j, l]260end261set_node_vars!(u, acc, equations, dg, i, j, upper_right_id)262end263264return nothing265end266267# Coarsen elements in the DG solver based on a list of cell_ids that should be removed.268# On `P4estMesh`, if an element coarsens the solution scaled by the Jacobian `J*u` is projected269# from the four children elements back onto the parent element. The solution on the parent270# element is then recovered by dividing by the new element Jacobian.271function coarsen!(u_ode::AbstractVector, adaptor,272mesh::Union{TreeMesh{2}, P4estMesh{2}},273equations, dg::DGSEM, cache, elements_to_remove)274# Return early if there is nothing to do275if isempty(elements_to_remove)276if mpi_isparallel()277# MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do278# locally (there still might be other MPI ranks that have coarsened elements)279reinitialize_containers!(mesh, equations, dg, cache)280end281return282end283284# Determine for each old element whether it needs to be removed285to_be_removed = falses(nelements(dg, cache))286to_be_removed[elements_to_remove] .= true287288# Retain current solution data and Jacobians289old_n_elements = nelements(dg, cache)290old_u_ode = copy(u_ode)291old_inverse_jacobian = copy(cache.elements.inverse_jacobian)292# OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed293GC.@preserve old_u_ode old_inverse_jacobian begin294old_u = wrap_array(old_u_ode, mesh, equations, dg, cache)295296if mesh isa P4estMesh297# Loop over all elements in old container and scale the old solution by the Jacobian298# prior to projection299for old_element_id in 1:old_n_elements300for v in eachvariable(equations)301old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./302old_inverse_jacobian[..,303old_element_id])304end305end306end307308@trixi_timeit timer() "reinitialize data structures" begin309reinitialize_containers!(mesh, equations, dg, cache)310end311312resize!(u_ode,313nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache))314u = wrap_array(u_ode, mesh, equations, dg, cache)315316# Loop over all elements in old container and either copy them or coarsen them317skip = 0318element_id = 1319for old_element_id in 1:old_n_elements320# If skip is non-zero, we just coarsened 2^ndims elements and need to omit the following elements321if skip > 0322skip -= 1323continue324end325326if to_be_removed[old_element_id]327# If an element is to be removed, sanity check if the following elements328# are also marked - otherwise there would be an error in the way the329# cells/elements are sorted330@assert all(to_be_removed[old_element_id:(old_element_id + 2^ndims(mesh) - 1)]) "bad cell/element order"331332# Coarsen elements and store solution directly in new data structure333coarsen_elements!(u, element_id, old_u, old_element_id,334adaptor, equations, dg)335336if mesh isa P4estMesh337# Before `element_id` is incremented, divide by the new Jacobian and save338# the result in the parent element339for v in eachvariable(equations)340u[v, .., element_id] .*= (4 .*341cache.elements.inverse_jacobian[..,342element_id])343end344end345346# Increment `element_id` on the coarsened mesh by one and `skip` = 3 in 2D347# because 4 children elements become 1 parent element348element_id += 1349skip = 2^ndims(mesh) - 1350else351if mesh isa P4estMesh352# Copy old element data to new element container and remove Jacobian scaling353for v in eachvariable(equations)354u[v, .., element_id] .= (old_u[v, .., old_element_id] .*355old_inverse_jacobian[..,356old_element_id])357end358else # isa TreeMesh359@views u[:, .., element_id] .= old_u[:, .., old_element_id]360end361# No coarsening occurred, so increment `element_id` on the new mesh by one362element_id += 1363end364end365# If everything is correct, we should have processed all elements.366@assert element_id==nelements(dg, cache) + 1 "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))"367end # GC.@preserve old_u_ode old_inverse_jacobian368369# Sanity check370if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 &&371!mpi_isparallel()372@assert ninterfaces(cache.interfaces)==ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements")373end374375return nothing376end377378function coarsen!(u_ode::AbstractVector, adaptor,379mesh::Union{TreeMesh{2}, P4estMesh{2}, TreeMesh{3}, P4estMesh{3}},380equations, dg::DGSEM, cache, cache_parabolic,381elements_to_remove)382# Call `coarsen!` for the hyperbolic part, which does the heavy lifting of383# actually transferring the solution to the coarsened cells384coarsen!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_remove)385386# Resize parabolic helper variables387@unpack parabolic_container = cache_parabolic388resize!(parabolic_container, equations, dg, cache)389390return nothing391end392393# TODO: Taal compare performance of different implementations394# Coarsen solution data u for four elements, using L2 projection395function coarsen_elements!(u::AbstractArray{<:Any, 4}, element_id,396old_u, old_element_id,397adaptor::LobattoLegendreAdaptorL2, equations, dg)398@unpack reverse_upper, reverse_lower = adaptor399400# Store old element ids401lower_left_id = old_element_id402lower_right_id = old_element_id + 1403upper_left_id = old_element_id + 2404upper_right_id = old_element_id + 3405406@boundscheck begin407@assert old_element_id >= 1408@assert size(old_u, 1) == nvariables(equations)409@assert size(old_u, 2) == nnodes(dg)410@assert size(old_u, 3) == nnodes(dg)411@assert size(old_u, 4) >= old_element_id + 3412@assert element_id >= 1413@assert size(u, 1) == nvariables(equations)414@assert size(u, 2) == nnodes(dg)415@assert size(u, 3) == nnodes(dg)416@assert size(u, 4) >= element_id417end418419for j in eachnode(dg), i in eachnode(dg)420acc = zero(get_node_vars(u, equations, dg, i, j, element_id))421422# Project from lower left element423for l in eachnode(dg), k in eachnode(dg)424acc += get_node_vars(old_u, equations, dg, k, l, lower_left_id) *425reverse_lower[i, k] * reverse_lower[j, l]426end427428# Project from lower right element429for l in eachnode(dg), k in eachnode(dg)430acc += get_node_vars(old_u, equations, dg, k, l, lower_right_id) *431reverse_upper[i, k] * reverse_lower[j, l]432end433434# Project from upper left element435for l in eachnode(dg), k in eachnode(dg)436acc += get_node_vars(old_u, equations, dg, k, l, upper_left_id) *437reverse_lower[i, k] * reverse_upper[j, l]438end439440# Project from upper right element441for l in eachnode(dg), k in eachnode(dg)442acc += get_node_vars(old_u, equations, dg, k, l, upper_right_id) *443reverse_upper[i, k] * reverse_upper[j, l]444end445446# Update value447set_node_vars!(u, acc, equations, dg, i, j, element_id)448end449end450451# Coarsen and refine elements in the DG solver based on a difference list.452function adapt!(u_ode::AbstractVector, adaptor, mesh::T8codeMesh{2}, equations,453dg::DGSEM, cache, difference)454455# Return early if there is nothing to do.456if !any(difference .!= 0)457if mpi_isparallel()458# MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do459# locally (there still might be other MPI ranks that have refined elements)460reinitialize_containers!(mesh, equations, dg, cache)461end462return463end464465# Number of (local) cells/elements.466old_nelems = nelements(dg, cache)467new_nelems = ncells(mesh)468469# Local element indices.470old_index = 1471new_index = 1472473# Note: This is true for `quads`.474T8_CHILDREN = 4475476# Retain current solution and inverse Jacobian data.477old_u_ode = copy(u_ode)478old_inverse_jacobian = copy(cache.elements.inverse_jacobian)479480# OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed481GC.@preserve old_u_ode begin482old_u = wrap_array(old_u_ode, mesh, equations, dg, cache)483484# Loop over all elements in old container and scale the old solution by the Jacobian485# prior to interpolation or projection486for old_element_id in 1:old_nelems487for v in eachvariable(equations)488old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./489old_inverse_jacobian[..,490old_element_id])491end492end493494@trixi_timeit timer() "reinitialize data structures" begin495reinitialize_containers!(mesh, equations, dg, cache)496end497498resize!(u_ode, nvariables(equations) * ndofs(mesh, dg, cache))499u = wrap_array(u_ode, mesh, equations, dg, cache)500501while old_index <= old_nelems && new_index <= new_nelems502if difference[old_index] > 0 # Refine.503504# Refine element and store solution directly in new data structure.505refine_element!(u, new_index, old_u, old_index, adaptor, equations, dg)506507# Before indices are incremented divide by the new Jacobians on each508# child element and save the result509for m in 0:3 # loop over the children510for v in eachvariable(equations)511u[v, .., new_index + m] .*= (0.25f0 .*512cache.elements.inverse_jacobian[..,513new_index + m])514end515end516517# Increment `old_index` on the original mesh and the `new_index`518# on the refined mesh with the number of children, i.e., T8_CHILDREN = 4519old_index += 1520new_index += T8_CHILDREN521522elseif difference[old_index] < 0 # Coarsen.523524# If an element is to be removed, sanity check if the following elements525# are also marked - otherwise there would be an error in the way the526# cells/elements are sorted.527@assert all(difference[old_index:(old_index + T8_CHILDREN - 1)] .< 0) "bad cell/element order"528529# Coarsen elements and store solution directly in new data structure.530coarsen_elements!(u, new_index, old_u, old_index, adaptor, equations,531dg)532533# Before the indices are incremented divide by the new Jacobian and save534# the result again in the parent element535for v in eachvariable(equations)536u[v, .., new_index] .*= (4 .* cache.elements.inverse_jacobian[..,537new_index])538end539540# Increment `old_index` on the original mesh with the number of children541# (T8_CHILDREN = 4 in 2D) and the `new_index` by one for the single542# coarsened element543old_index += T8_CHILDREN544new_index += 1545546else # No changes.547548# Copy old element data to new element container and remove Jacobian scaling549for v in eachvariable(equations)550u[v, .., new_index] .= (old_u[v, .., old_index] .*551old_inverse_jacobian[.., old_index])552end553554# No refinement / coarsening occurred, so increment element index555# on each mesh by one556old_index += 1557new_index += 1558end559end # while560end # GC.@preserve old_u_ode old_inverse_jacobian561562return nothing563end564end # @muladd565566567