Path: blob/main/src/solvers/dgsem_unstructured/containers_2d.jl
5590 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# Container data structure (structure-of-arrays style) for DG elements on curved unstructured mesh8struct UnstructuredElementContainer2D{RealT <: Real, uEltype <: Real} <:9AbstractElementContainer10node_coordinates::Array{RealT, 4} # [ndims, nnodes, nnodes, nelement]11jacobian_matrix::Array{RealT, 5} # [ndims, ndims, nnodes, nnodes, nelement]12inverse_jacobian::Array{RealT, 3} # [nnodes, nnodes, nelement]13contravariant_vectors::Array{RealT, 5} # [ndims, ndims, nnodes, nnodes, nelement]14normal_directions::Array{RealT, 4} # [ndims, nnodes, local sides, nelement]15surface_flux_values::Array{uEltype, 4} # [variables, nnodes, local sides, elements]16end1718# construct an empty curved element container to be filled later with geometries in the19# unstructured mesh constructor20function UnstructuredElementContainer2D{RealT, uEltype}(capacity::Integer, n_variables,21n_nodes) where {RealT <: Real,22uEltype <: Real}23nan_RealT = convert(RealT, NaN)24nan_uEltype = convert(uEltype, NaN)2526node_coordinates = fill(nan_RealT, (2, n_nodes, n_nodes, capacity))27jacobian_matrix = fill(nan_RealT, (2, 2, n_nodes, n_nodes, capacity))28inverse_jacobian = fill(nan_RealT, (n_nodes, n_nodes, capacity))29contravariant_vectors = fill(nan_RealT, (2, 2, n_nodes, n_nodes, capacity))30normal_directions = fill(nan_RealT, (2, n_nodes, 4, capacity))31surface_flux_values = fill(nan_uEltype, (n_variables, n_nodes, 4, capacity))3233return UnstructuredElementContainer2D{RealT, uEltype}(node_coordinates,34jacobian_matrix,35inverse_jacobian,36contravariant_vectors,37normal_directions,38surface_flux_values)39end4041@inline function nelements(elements::UnstructuredElementContainer2D)42return size(elements.surface_flux_values, 4)43end44"""45eachelement(elements::UnstructuredElementContainer2D)4647Return an iterator over the indices that specify the location in relevant data structures48for the elements in `elements`.49In particular, not the elements themselves are returned.50"""51@inline function eachelement(elements::UnstructuredElementContainer2D)52return Base.OneTo(nelements(elements))53end5455@inline function nvariables(elements::UnstructuredElementContainer2D)56return size(elements.surface_flux_values, 1)57end58@inline function nnodes(elements::UnstructuredElementContainer2D)59return size(elements.surface_flux_values, 2)60end6162Base.real(elements::UnstructuredElementContainer2D) = eltype(elements.node_coordinates)63function Base.eltype(elements::UnstructuredElementContainer2D)64return eltype(elements.surface_flux_values)65end6667@inline function get_surface_normal(vec, indices...)68# way to extract the normal vector at the surfaces without allocating69surface_vector = SVector(ntuple(j -> vec[j, indices...], 2))70return surface_vector71end7273function init_elements(mesh::UnstructuredMesh2D, equations, basis, RealT, uEltype)74elements = UnstructuredElementContainer2D{RealT, uEltype}(mesh.n_elements,75nvariables(equations),76nnodes(basis))77init_elements!(elements, mesh, basis)78return elements79end8081function init_elements!(elements::UnstructuredElementContainer2D, mesh, basis)82four_corners = zeros(eltype(mesh.corners), 4, 2)8384# loop through elements and call the correct constructor based on whether the element is curved85for element in eachelement(elements)86if mesh.element_is_curved[element]87init_element!(elements, element, basis,88view(mesh.surface_curves, :, element))89else # straight sided element90for i in 1:4, j in 1:291# pull the (x,y) values of these corners out of the global corners array92four_corners[i, j] = mesh.corners[j, mesh.element_node_ids[i, element]]93end94init_element!(elements, element, basis, four_corners)95end96end97end9899# initialize all the values in the container of a general element (either straight sided or curved)100function init_element!(elements, element, basis::LobattoLegendreBasis,101corners_or_surface_curves)102calc_node_coordinates!(elements.node_coordinates, element, get_nodes(basis),103corners_or_surface_curves)104105calc_metric_terms!(elements.jacobian_matrix, element, get_nodes(basis),106corners_or_surface_curves)107108calc_inverse_jacobian!(elements.inverse_jacobian, element, elements.jacobian_matrix)109110calc_contravariant_vectors!(elements.contravariant_vectors, element,111elements.jacobian_matrix)112113calc_normal_directions!(elements.normal_directions, element, get_nodes(basis),114corners_or_surface_curves)115116return elements117end118119# generic container for the interior interfaces of an unstructured mesh120struct UnstructuredInterfaceContainer2D{uEltype <: Real} <:121AbstractInterfaceContainer122u::Array{uEltype, 4} # [primary/secondary, variables, i, interfaces]123start_index::Vector{Int} # [interfaces]124index_increment::Vector{Int} # [interfaces]125element_ids::Array{Int, 2} # [primary/secondary, interfaces]126element_side_ids::Array{Int, 2} # [primary/secondary, interfaces]127end128129# Construct an empty curved interface container to be filled later with neighbour130# information in the unstructured mesh constructor131function UnstructuredInterfaceContainer2D{uEltype}(capacity::Integer, n_variables,132n_nodes) where {uEltype <: Real}133nan_uEltype = convert(uEltype, NaN)134135u = fill(nan_uEltype, (2, n_variables, n_nodes, capacity))136start_index = fill(typemin(Int), capacity)137index_increment = fill(typemin(Int), capacity)138element_ids = fill(typemin(Int), (2, capacity))139element_side_ids = fill(typemin(Int), (2, capacity))140141return UnstructuredInterfaceContainer2D{uEltype}(u, start_index, index_increment,142element_ids, element_side_ids)143end144145@inline function ninterfaces(interfaces::UnstructuredInterfaceContainer2D)146return length(interfaces.start_index)147end148@inline nnodes(interfaces::UnstructuredInterfaceContainer2D) = size(interfaces.u, 3)149150function init_interfaces(mesh::UnstructuredMesh2D,151elements::UnstructuredElementContainer2D)152interfaces = UnstructuredInterfaceContainer2D{eltype(elements)}(mesh.n_interfaces,153nvariables(elements),154nnodes(elements))155156# extract and save the appropriate neighbour information from the mesh skeleton157if isperiodic(mesh)158init_interfaces!(interfaces, mesh.neighbour_information, mesh.boundary_names,159mesh.n_elements, True())160else161init_interfaces!(interfaces, mesh.neighbour_information, mesh.boundary_names,162mesh.n_elements, False())163end164165return interfaces166end167168function init_interfaces!(interfaces, edge_information, boundary_names, n_elements,169periodic::False)170n_nodes = nnodes(interfaces)171n_surfaces = size(edge_information, 2)172intr_count = 1173for j in 1:n_surfaces174if edge_information[4, j] > 0175# get the primary/secondary element information and coupling for an interior interface176interfaces.element_ids[1, intr_count] = edge_information[3, j] # primary element id177interfaces.element_ids[2, intr_count] = edge_information[4, j] # secondary element id178interfaces.element_side_ids[1, intr_count] = edge_information[5, j] # primary side id179interfaces.element_side_ids[2, intr_count] = abs(edge_information[6, j]) # secondary side id180# default the start and increment indexing181interfaces.start_index[intr_count] = 1182interfaces.index_increment[intr_count] = 1183if edge_information[6, j] < 0184# coordinate system in the secondary element is "flipped" compared to the primary element.185# Adjust the start and increment indexes such that the secondary element coordinate system186# can match the primary neighbour when surface coupling is computed187interfaces.start_index[intr_count] = n_nodes188interfaces.index_increment[intr_count] = -1189end190intr_count += 1191end192end193194return nothing195end196197function init_interfaces!(interfaces, edge_information, boundary_names, n_elements,198periodic::True)199n_nodes = nnodes(interfaces)200n_surfaces = size(edge_information, 2)201# for now this set a fully periodic domain202# TODO: possibly adjust to be able to set periodic in only the x or y direction203for j in 1:n_surfaces204if edge_information[4, j] > 0205# get the primary/secondary element information and coupling for an interior interface206interfaces.element_ids[1, j] = edge_information[3, j] # primary element id207interfaces.element_ids[2, j] = edge_information[4, j] # secondary element id208interfaces.element_side_ids[1, j] = edge_information[5, j] # primary side id209interfaces.element_side_ids[2, j] = abs(edge_information[6, j]) # secondary side id210# default the start and increment indexing211interfaces.start_index[j] = 1212interfaces.index_increment[j] = 1213if edge_information[6, j] < 0214# coordinate system in the secondary element is "flipped" compared to the primary element.215# Adjust the start and increment indexes such that the secondary element coordinate system216# can match the primary neighbour when surface coupling is computed217interfaces.start_index[j] = n_nodes218interfaces.index_increment[j] = -1219end220else221# way to set periodic BCs where we are assuming to have a structured mesh with internal curves222primary_side = edge_information[5, j]223primary_element = edge_information[3, j]224# Note: This is a way to get the neighbour element number and local side from a square225# structured mesh where the element local surface numbering is right-handed226if boundary_names[primary_side, primary_element] === :Bottom227secondary_element = primary_element +228(n_elements - convert(Int, sqrt(n_elements)))229secondary_side = 3230elseif boundary_names[primary_side, primary_element] === :Top231secondary_element = primary_element -232(n_elements - convert(Int, sqrt(n_elements)))233secondary_side = 1234elseif boundary_names[primary_side, primary_element] === :Left235secondary_element = primary_element +236(convert(Int, sqrt(n_elements)) - 1)237secondary_side = 2238elseif boundary_names[primary_side, primary_element] === :Right239secondary_element = primary_element -240(convert(Int, sqrt(n_elements)) - 1)241secondary_side = 4242end243interfaces.element_ids[1, j] = primary_element244interfaces.element_ids[2, j] = secondary_element245interfaces.element_side_ids[1, j] = primary_side246interfaces.element_side_ids[2, j] = secondary_side247# set the start and increment indexing248# Note! We assume that the periodic mesh has no flipped element coordinate systems249interfaces.start_index[j] = 1250interfaces.index_increment[j] = 1251end252end253254return nothing255end256257# TODO: Clean-up meshes. Find a better name since it's also used for other meshes258# generic container for the boundary interfaces of an unstructured mesh259struct UnstructuredBoundaryContainer2D{RealT <: Real, uEltype <: Real} <:260AbstractBoundaryContainer261u::Array{uEltype, 3} # [variables, i, boundaries]262element_id::Vector{Int} # [boundaries]263element_side_id::Vector{Int} # [boundaries]264node_coordinates::Array{RealT, 3} # [ndims, nnodes, boundaries]265name::Vector{Symbol} # [boundaries]266end267268# construct an empty curved boundary container to be filled later with neighbour269# information in the unstructured mesh constructor270function UnstructuredBoundaryContainer2D{RealT, uEltype}(capacity::Integer, n_variables,271n_nodes) where {RealT <: Real,272uEltype <:273Real}274nan_RealT = convert(RealT, NaN)275nan_uEltype = convert(uEltype, NaN)276277u = fill(nan_uEltype, (n_variables, n_nodes, capacity))278element_id = fill(typemin(Int), capacity)279element_side_id = fill(typemin(Int), capacity)280node_coordinates = fill(nan_RealT, (2, n_nodes, capacity))281name = fill(:empty, capacity)282283return UnstructuredBoundaryContainer2D{RealT, uEltype}(u, element_id,284element_side_id,285node_coordinates, name)286end287288@inline function nboundaries(boundaries::UnstructuredBoundaryContainer2D)289return length(boundaries.name)290end291292function init_boundaries(mesh::UnstructuredMesh2D,293elements::UnstructuredElementContainer2D)294boundaries = UnstructuredBoundaryContainer2D{real(elements), eltype(elements)}(mesh.n_boundaries,295nvariables(elements),296nnodes(elements))297298# extract and save the appropriate boundary information provided any physical boundaries exist299if mesh.n_boundaries > 0300init_boundaries!(boundaries, mesh.neighbour_information, mesh.boundary_names,301elements)302end303return boundaries304end305306function init_boundaries!(boundaries::UnstructuredBoundaryContainer2D, edge_information,307boundary_names, elements)308n_surfaces = size(edge_information, 2)309bndy_count = 1310for j in 1:n_surfaces311if edge_information[4, j] == 0312# get the primary element information at a boundary interface313primary_element = edge_information[3, j]314primary_side = edge_information[5, j]315boundaries.element_id[bndy_count] = primary_element316boundaries.element_side_id[bndy_count] = primary_side317318# extract the physical boundary's name from the global list319boundaries.name[bndy_count] = boundary_names[primary_side, primary_element]320321# Store copy of the (x,y) node coordinates on the physical boundary322enc = elements.node_coordinates323if primary_side == 1324boundaries.node_coordinates[:, :, bndy_count] .= enc[:, :, 1,325primary_element]326elseif primary_side == 2327boundaries.node_coordinates[:, :, bndy_count] .= enc[:, end, :,328primary_element]329elseif primary_side == 3330boundaries.node_coordinates[:, :, bndy_count] .= enc[:, :, end,331primary_element]332else # primary_side == 4333boundaries.node_coordinates[:, :, bndy_count] .= enc[:, 1, :,334primary_element]335end336bndy_count += 1337end338end339340return nothing341end342end # @muladd343344345