Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem_unstructured/containers_2d.jl
5590 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
# Container data structure (structure-of-arrays style) for DG elements on curved unstructured mesh
9
struct UnstructuredElementContainer2D{RealT <: Real, uEltype <: Real} <:
10
AbstractElementContainer
11
node_coordinates::Array{RealT, 4} # [ndims, nnodes, nnodes, nelement]
12
jacobian_matrix::Array{RealT, 5} # [ndims, ndims, nnodes, nnodes, nelement]
13
inverse_jacobian::Array{RealT, 3} # [nnodes, nnodes, nelement]
14
contravariant_vectors::Array{RealT, 5} # [ndims, ndims, nnodes, nnodes, nelement]
15
normal_directions::Array{RealT, 4} # [ndims, nnodes, local sides, nelement]
16
surface_flux_values::Array{uEltype, 4} # [variables, nnodes, local sides, elements]
17
end
18
19
# construct an empty curved element container to be filled later with geometries in the
20
# unstructured mesh constructor
21
function UnstructuredElementContainer2D{RealT, uEltype}(capacity::Integer, n_variables,
22
n_nodes) where {RealT <: Real,
23
uEltype <: Real}
24
nan_RealT = convert(RealT, NaN)
25
nan_uEltype = convert(uEltype, NaN)
26
27
node_coordinates = fill(nan_RealT, (2, n_nodes, n_nodes, capacity))
28
jacobian_matrix = fill(nan_RealT, (2, 2, n_nodes, n_nodes, capacity))
29
inverse_jacobian = fill(nan_RealT, (n_nodes, n_nodes, capacity))
30
contravariant_vectors = fill(nan_RealT, (2, 2, n_nodes, n_nodes, capacity))
31
normal_directions = fill(nan_RealT, (2, n_nodes, 4, capacity))
32
surface_flux_values = fill(nan_uEltype, (n_variables, n_nodes, 4, capacity))
33
34
return UnstructuredElementContainer2D{RealT, uEltype}(node_coordinates,
35
jacobian_matrix,
36
inverse_jacobian,
37
contravariant_vectors,
38
normal_directions,
39
surface_flux_values)
40
end
41
42
@inline function nelements(elements::UnstructuredElementContainer2D)
43
return size(elements.surface_flux_values, 4)
44
end
45
"""
46
eachelement(elements::UnstructuredElementContainer2D)
47
48
Return an iterator over the indices that specify the location in relevant data structures
49
for the elements in `elements`.
50
In particular, not the elements themselves are returned.
51
"""
52
@inline function eachelement(elements::UnstructuredElementContainer2D)
53
return Base.OneTo(nelements(elements))
54
end
55
56
@inline function nvariables(elements::UnstructuredElementContainer2D)
57
return size(elements.surface_flux_values, 1)
58
end
59
@inline function nnodes(elements::UnstructuredElementContainer2D)
60
return size(elements.surface_flux_values, 2)
61
end
62
63
Base.real(elements::UnstructuredElementContainer2D) = eltype(elements.node_coordinates)
64
function Base.eltype(elements::UnstructuredElementContainer2D)
65
return eltype(elements.surface_flux_values)
66
end
67
68
@inline function get_surface_normal(vec, indices...)
69
# way to extract the normal vector at the surfaces without allocating
70
surface_vector = SVector(ntuple(j -> vec[j, indices...], 2))
71
return surface_vector
72
end
73
74
function init_elements(mesh::UnstructuredMesh2D, equations, basis, RealT, uEltype)
75
elements = UnstructuredElementContainer2D{RealT, uEltype}(mesh.n_elements,
76
nvariables(equations),
77
nnodes(basis))
78
init_elements!(elements, mesh, basis)
79
return elements
80
end
81
82
function init_elements!(elements::UnstructuredElementContainer2D, mesh, basis)
83
four_corners = zeros(eltype(mesh.corners), 4, 2)
84
85
# loop through elements and call the correct constructor based on whether the element is curved
86
for element in eachelement(elements)
87
if mesh.element_is_curved[element]
88
init_element!(elements, element, basis,
89
view(mesh.surface_curves, :, element))
90
else # straight sided element
91
for i in 1:4, j in 1:2
92
# pull the (x,y) values of these corners out of the global corners array
93
four_corners[i, j] = mesh.corners[j, mesh.element_node_ids[i, element]]
94
end
95
init_element!(elements, element, basis, four_corners)
96
end
97
end
98
end
99
100
# initialize all the values in the container of a general element (either straight sided or curved)
101
function init_element!(elements, element, basis::LobattoLegendreBasis,
102
corners_or_surface_curves)
103
calc_node_coordinates!(elements.node_coordinates, element, get_nodes(basis),
104
corners_or_surface_curves)
105
106
calc_metric_terms!(elements.jacobian_matrix, element, get_nodes(basis),
107
corners_or_surface_curves)
108
109
calc_inverse_jacobian!(elements.inverse_jacobian, element, elements.jacobian_matrix)
110
111
calc_contravariant_vectors!(elements.contravariant_vectors, element,
112
elements.jacobian_matrix)
113
114
calc_normal_directions!(elements.normal_directions, element, get_nodes(basis),
115
corners_or_surface_curves)
116
117
return elements
118
end
119
120
# generic container for the interior interfaces of an unstructured mesh
121
struct UnstructuredInterfaceContainer2D{uEltype <: Real} <:
122
AbstractInterfaceContainer
123
u::Array{uEltype, 4} # [primary/secondary, variables, i, interfaces]
124
start_index::Vector{Int} # [interfaces]
125
index_increment::Vector{Int} # [interfaces]
126
element_ids::Array{Int, 2} # [primary/secondary, interfaces]
127
element_side_ids::Array{Int, 2} # [primary/secondary, interfaces]
128
end
129
130
# Construct an empty curved interface container to be filled later with neighbour
131
# information in the unstructured mesh constructor
132
function UnstructuredInterfaceContainer2D{uEltype}(capacity::Integer, n_variables,
133
n_nodes) where {uEltype <: Real}
134
nan_uEltype = convert(uEltype, NaN)
135
136
u = fill(nan_uEltype, (2, n_variables, n_nodes, capacity))
137
start_index = fill(typemin(Int), capacity)
138
index_increment = fill(typemin(Int), capacity)
139
element_ids = fill(typemin(Int), (2, capacity))
140
element_side_ids = fill(typemin(Int), (2, capacity))
141
142
return UnstructuredInterfaceContainer2D{uEltype}(u, start_index, index_increment,
143
element_ids, element_side_ids)
144
end
145
146
@inline function ninterfaces(interfaces::UnstructuredInterfaceContainer2D)
147
return length(interfaces.start_index)
148
end
149
@inline nnodes(interfaces::UnstructuredInterfaceContainer2D) = size(interfaces.u, 3)
150
151
function init_interfaces(mesh::UnstructuredMesh2D,
152
elements::UnstructuredElementContainer2D)
153
interfaces = UnstructuredInterfaceContainer2D{eltype(elements)}(mesh.n_interfaces,
154
nvariables(elements),
155
nnodes(elements))
156
157
# extract and save the appropriate neighbour information from the mesh skeleton
158
if isperiodic(mesh)
159
init_interfaces!(interfaces, mesh.neighbour_information, mesh.boundary_names,
160
mesh.n_elements, True())
161
else
162
init_interfaces!(interfaces, mesh.neighbour_information, mesh.boundary_names,
163
mesh.n_elements, False())
164
end
165
166
return interfaces
167
end
168
169
function init_interfaces!(interfaces, edge_information, boundary_names, n_elements,
170
periodic::False)
171
n_nodes = nnodes(interfaces)
172
n_surfaces = size(edge_information, 2)
173
intr_count = 1
174
for j in 1:n_surfaces
175
if edge_information[4, j] > 0
176
# get the primary/secondary element information and coupling for an interior interface
177
interfaces.element_ids[1, intr_count] = edge_information[3, j] # primary element id
178
interfaces.element_ids[2, intr_count] = edge_information[4, j] # secondary element id
179
interfaces.element_side_ids[1, intr_count] = edge_information[5, j] # primary side id
180
interfaces.element_side_ids[2, intr_count] = abs(edge_information[6, j]) # secondary side id
181
# default the start and increment indexing
182
interfaces.start_index[intr_count] = 1
183
interfaces.index_increment[intr_count] = 1
184
if edge_information[6, j] < 0
185
# coordinate system in the secondary element is "flipped" compared to the primary element.
186
# Adjust the start and increment indexes such that the secondary element coordinate system
187
# can match the primary neighbour when surface coupling is computed
188
interfaces.start_index[intr_count] = n_nodes
189
interfaces.index_increment[intr_count] = -1
190
end
191
intr_count += 1
192
end
193
end
194
195
return nothing
196
end
197
198
function init_interfaces!(interfaces, edge_information, boundary_names, n_elements,
199
periodic::True)
200
n_nodes = nnodes(interfaces)
201
n_surfaces = size(edge_information, 2)
202
# for now this set a fully periodic domain
203
# TODO: possibly adjust to be able to set periodic in only the x or y direction
204
for j in 1:n_surfaces
205
if edge_information[4, j] > 0
206
# get the primary/secondary element information and coupling for an interior interface
207
interfaces.element_ids[1, j] = edge_information[3, j] # primary element id
208
interfaces.element_ids[2, j] = edge_information[4, j] # secondary element id
209
interfaces.element_side_ids[1, j] = edge_information[5, j] # primary side id
210
interfaces.element_side_ids[2, j] = abs(edge_information[6, j]) # secondary side id
211
# default the start and increment indexing
212
interfaces.start_index[j] = 1
213
interfaces.index_increment[j] = 1
214
if edge_information[6, j] < 0
215
# coordinate system in the secondary element is "flipped" compared to the primary element.
216
# Adjust the start and increment indexes such that the secondary element coordinate system
217
# can match the primary neighbour when surface coupling is computed
218
interfaces.start_index[j] = n_nodes
219
interfaces.index_increment[j] = -1
220
end
221
else
222
# way to set periodic BCs where we are assuming to have a structured mesh with internal curves
223
primary_side = edge_information[5, j]
224
primary_element = edge_information[3, j]
225
# Note: This is a way to get the neighbour element number and local side from a square
226
# structured mesh where the element local surface numbering is right-handed
227
if boundary_names[primary_side, primary_element] === :Bottom
228
secondary_element = primary_element +
229
(n_elements - convert(Int, sqrt(n_elements)))
230
secondary_side = 3
231
elseif boundary_names[primary_side, primary_element] === :Top
232
secondary_element = primary_element -
233
(n_elements - convert(Int, sqrt(n_elements)))
234
secondary_side = 1
235
elseif boundary_names[primary_side, primary_element] === :Left
236
secondary_element = primary_element +
237
(convert(Int, sqrt(n_elements)) - 1)
238
secondary_side = 2
239
elseif boundary_names[primary_side, primary_element] === :Right
240
secondary_element = primary_element -
241
(convert(Int, sqrt(n_elements)) - 1)
242
secondary_side = 4
243
end
244
interfaces.element_ids[1, j] = primary_element
245
interfaces.element_ids[2, j] = secondary_element
246
interfaces.element_side_ids[1, j] = primary_side
247
interfaces.element_side_ids[2, j] = secondary_side
248
# set the start and increment indexing
249
# Note! We assume that the periodic mesh has no flipped element coordinate systems
250
interfaces.start_index[j] = 1
251
interfaces.index_increment[j] = 1
252
end
253
end
254
255
return nothing
256
end
257
258
# TODO: Clean-up meshes. Find a better name since it's also used for other meshes
259
# generic container for the boundary interfaces of an unstructured mesh
260
struct UnstructuredBoundaryContainer2D{RealT <: Real, uEltype <: Real} <:
261
AbstractBoundaryContainer
262
u::Array{uEltype, 3} # [variables, i, boundaries]
263
element_id::Vector{Int} # [boundaries]
264
element_side_id::Vector{Int} # [boundaries]
265
node_coordinates::Array{RealT, 3} # [ndims, nnodes, boundaries]
266
name::Vector{Symbol} # [boundaries]
267
end
268
269
# construct an empty curved boundary container to be filled later with neighbour
270
# information in the unstructured mesh constructor
271
function UnstructuredBoundaryContainer2D{RealT, uEltype}(capacity::Integer, n_variables,
272
n_nodes) where {RealT <: Real,
273
uEltype <:
274
Real}
275
nan_RealT = convert(RealT, NaN)
276
nan_uEltype = convert(uEltype, NaN)
277
278
u = fill(nan_uEltype, (n_variables, n_nodes, capacity))
279
element_id = fill(typemin(Int), capacity)
280
element_side_id = fill(typemin(Int), capacity)
281
node_coordinates = fill(nan_RealT, (2, n_nodes, capacity))
282
name = fill(:empty, capacity)
283
284
return UnstructuredBoundaryContainer2D{RealT, uEltype}(u, element_id,
285
element_side_id,
286
node_coordinates, name)
287
end
288
289
@inline function nboundaries(boundaries::UnstructuredBoundaryContainer2D)
290
return length(boundaries.name)
291
end
292
293
function init_boundaries(mesh::UnstructuredMesh2D,
294
elements::UnstructuredElementContainer2D)
295
boundaries = UnstructuredBoundaryContainer2D{real(elements), eltype(elements)}(mesh.n_boundaries,
296
nvariables(elements),
297
nnodes(elements))
298
299
# extract and save the appropriate boundary information provided any physical boundaries exist
300
if mesh.n_boundaries > 0
301
init_boundaries!(boundaries, mesh.neighbour_information, mesh.boundary_names,
302
elements)
303
end
304
return boundaries
305
end
306
307
function init_boundaries!(boundaries::UnstructuredBoundaryContainer2D, edge_information,
308
boundary_names, elements)
309
n_surfaces = size(edge_information, 2)
310
bndy_count = 1
311
for j in 1:n_surfaces
312
if edge_information[4, j] == 0
313
# get the primary element information at a boundary interface
314
primary_element = edge_information[3, j]
315
primary_side = edge_information[5, j]
316
boundaries.element_id[bndy_count] = primary_element
317
boundaries.element_side_id[bndy_count] = primary_side
318
319
# extract the physical boundary's name from the global list
320
boundaries.name[bndy_count] = boundary_names[primary_side, primary_element]
321
322
# Store copy of the (x,y) node coordinates on the physical boundary
323
enc = elements.node_coordinates
324
if primary_side == 1
325
boundaries.node_coordinates[:, :, bndy_count] .= enc[:, :, 1,
326
primary_element]
327
elseif primary_side == 2
328
boundaries.node_coordinates[:, :, bndy_count] .= enc[:, end, :,
329
primary_element]
330
elseif primary_side == 3
331
boundaries.node_coordinates[:, :, bndy_count] .= enc[:, :, end,
332
primary_element]
333
else # primary_side == 4
334
boundaries.node_coordinates[:, :, bndy_count] .= enc[:, 1, :,
335
primary_element]
336
end
337
bndy_count += 1
338
end
339
end
340
341
return nothing
342
end
343
end # @muladd
344
345