Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/fdsbp_unstructured/fdsbp_2d.jl
5591 views
1
# !!! warning "Experimental implementation (curvilinear FDSBP)"
2
# This is an experimental feature and may change in future releases.
3
4
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
5
# Since these FMAs can increase the performance of many numerical algorithms,
6
# we need to opt-in explicitly.
7
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
8
@muladd begin
9
#! format: noindent
10
11
# 2D unstructured cache
12
function create_cache(mesh::UnstructuredMesh2D, equations, dg::FDSBP, RealT, uEltype)
13
elements = init_elements(mesh, equations, dg.basis, RealT, uEltype)
14
15
interfaces = init_interfaces(mesh, elements)
16
17
boundaries = init_boundaries(mesh, elements)
18
19
# Container cache
20
cache = (; elements, interfaces, boundaries)
21
22
# Add Volume-Integral cache
23
cache = (; cache...,
24
create_cache(mesh, equations, dg.volume_integral, dg, cache, uEltype)...)
25
26
return cache
27
end
28
29
# 2D volume integral contributions for `VolumeIntegralStrongForm`
30
# OBS! This is the standard (not de-aliased) form of the volume integral.
31
# So it is not provably stable for variable coefficients due to the the metric terms.
32
function calc_volume_integral!(backend::Nothing, du, u,
33
mesh::UnstructuredMesh2D,
34
have_nonconservative_terms::False, equations,
35
volume_integral::VolumeIntegralStrongForm,
36
dg::FDSBP, cache)
37
D = dg.basis # SBP derivative operator
38
@unpack f_threaded = cache
39
@unpack contravariant_vectors = cache.elements
40
41
# SBP operators from SummationByPartsOperators.jl implement the basic interface
42
# of matrix-vector multiplication. Thus, we pass an "array of structures",
43
# packing all variables per node in an `SVector`.
44
if nvariables(equations) == 1
45
# `reinterpret(reshape, ...)` removes the leading dimension only if more
46
# than one variable is used.
47
u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u),
48
nnodes(dg), nnodes(dg), nelements(dg, cache))
49
du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)},
50
du),
51
nnodes(dg), nnodes(dg), nelements(dg, cache))
52
else
53
u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u)
54
du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)},
55
du)
56
end
57
58
# Use the tensor product structure to compute the discrete derivatives of
59
# the contravariant fluxes line-by-line and add them to `du` for each element.
60
@threaded for element in eachelement(dg, cache)
61
f_element = f_threaded[Threads.threadid()]
62
u_element = view(u_vectors, :, :, element)
63
64
# x direction
65
for j in eachnode(dg)
66
for i in eachnode(dg)
67
Ja1 = get_contravariant_vector(1, contravariant_vectors, i, j, element)
68
f_element[i, j] = flux(u_element[i, j], Ja1, equations)
69
end
70
mul!(view(du_vectors, :, j, element), D, view(f_element, :, j),
71
one(eltype(du)), one(eltype(du)))
72
end
73
74
# y direction
75
for i in eachnode(dg)
76
for j in eachnode(dg)
77
Ja2 = get_contravariant_vector(2, contravariant_vectors, i, j, element)
78
f_element[i, j] = flux(u_element[i, j], Ja2, equations)
79
end
80
mul!(view(du_vectors, i, :, element), D, view(f_element, i, :),
81
one(eltype(du)), one(eltype(du)))
82
end
83
end
84
85
return nothing
86
end
87
88
# 2D volume integral contributions for `VolumeIntegralUpwind`.
89
# Note that the plus / minus notation of the operators does not refer to the
90
# upwind / downwind directions of the fluxes.
91
# Instead, the plus / minus refers to the direction of the biasing within
92
# the finite difference stencils. Thus, the D^- operator acts on the positive
93
# part of the flux splitting f^+ and the D^+ operator acts on the negative part
94
# of the flux splitting f^-.
95
function calc_volume_integral!(backend::Nothing, du, u,
96
mesh::UnstructuredMesh2D,
97
have_nonconservative_terms::False, equations,
98
volume_integral::VolumeIntegralUpwind,
99
dg::FDSBP, cache)
100
# Assume that
101
# dg.basis isa SummationByPartsOperators.UpwindOperators
102
D_minus = dg.basis.minus # Upwind SBP D^- derivative operator
103
D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator
104
@unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache
105
@unpack splitting = volume_integral
106
@unpack contravariant_vectors = cache.elements
107
108
# SBP operators from SummationByPartsOperators.jl implement the basic interface
109
# of matrix-vector multiplication. Thus, we pass an "array of structures",
110
# packing all variables per node in an `SVector`.
111
if nvariables(equations) == 1
112
# `reinterpret(reshape, ...)` removes the leading dimension only if more
113
# than one variable is used.
114
u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u),
115
nnodes(dg), nnodes(dg), nelements(dg, cache))
116
du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)},
117
du),
118
nnodes(dg), nnodes(dg), nelements(dg, cache))
119
else
120
u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u)
121
du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)},
122
du)
123
end
124
125
# Use the tensor product structure to compute the discrete derivatives of
126
# the fluxes line-by-line and add them to `du` for each element.
127
@threaded for element in eachelement(dg, cache)
128
# f_minus_plus_element wraps the storage provided by f_minus_element and
129
# f_plus_element such that we can use a single assignment below.
130
# f_minus_element and f_plus_element are updated whenever we update
131
# `f_minus_plus_element[i, j] = ...` below.
132
f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()]
133
f_minus_element = f_minus_threaded[Threads.threadid()]
134
f_plus_element = f_plus_threaded[Threads.threadid()]
135
u_element = view(u_vectors, :, :, element)
136
137
# x direction
138
# We use flux vector splittings in the directions of the contravariant
139
# basis vectors. Thus, we do not use a broadcasting operation like
140
# @. f_minus_plus_element = splitting(u_element, 1, equations)
141
# in the Cartesian case but loop over all nodes.
142
for j in eachnode(dg), i in eachnode(dg)
143
# contravariant vectors computed with central D matrix
144
Ja1 = get_contravariant_vector(1, contravariant_vectors, i, j, element)
145
f_minus_plus_element[i, j] = splitting(u_element[i, j], Ja1, equations)
146
end
147
148
for j in eachnode(dg)
149
mul!(view(du_vectors, :, j, element), D_minus, view(f_plus_element, :, j),
150
one(eltype(du)), one(eltype(du)))
151
mul!(view(du_vectors, :, j, element), D_plus, view(f_minus_element, :, j),
152
one(eltype(du)), one(eltype(du)))
153
end
154
155
# y direction
156
for j in eachnode(dg), i in eachnode(dg)
157
# contravariant vectors computed with central D matrix
158
Ja2 = get_contravariant_vector(2, contravariant_vectors, i, j, element)
159
f_minus_plus_element[i, j] = splitting(u_element[i, j], Ja2, equations)
160
end
161
162
for i in eachnode(dg)
163
mul!(view(du_vectors, i, :, element), D_minus, view(f_plus_element, i, :),
164
one(eltype(du)), one(eltype(du)))
165
mul!(view(du_vectors, i, :, element), D_plus, view(f_minus_element, i, :),
166
one(eltype(du)), one(eltype(du)))
167
end
168
end
169
170
return nothing
171
end
172
173
# Note! The local side numbering for the unstructured quadrilateral element implementation differs
174
# from the structured TreeMesh or StructuredMesh local side numbering:
175
#
176
# TreeMesh/StructuredMesh sides versus UnstructuredMesh sides
177
# 4 3
178
# ----------------- -----------------
179
# | | | |
180
# | ^ eta | | ^ eta |
181
# 1 | | | 2 4 | | | 2
182
# | | | | | |
183
# | ---> xi | | ---> xi |
184
# ----------------- -----------------
185
# 3 1
186
# Therefore, we require a different surface integral routine here despite their similar structure.
187
# Also, the normal directions are already outward pointing for `UnstructuredMesh2D` so all the
188
# surface contributions are added.
189
function calc_surface_integral!(backend::Nothing, du, u, mesh::UnstructuredMesh2D,
190
equations, surface_integral::SurfaceIntegralStrongForm,
191
dg::DG, cache)
192
inv_weight_left = inv(left_boundary_weight(dg.basis))
193
inv_weight_right = inv(right_boundary_weight(dg.basis))
194
@unpack normal_directions, surface_flux_values = cache.elements
195
196
@threaded for element in eachelement(dg, cache)
197
for l in eachnode(dg)
198
# surface at -x
199
u_node = get_node_vars(u, equations, dg, 1, l, element)
200
# compute internal flux in normal direction on side 4
201
outward_direction = get_surface_normal(normal_directions, l, 4, element)
202
f_node = flux(u_node, outward_direction, equations)
203
f_num = get_node_vars(surface_flux_values, equations, dg, l, 4, element)
204
multiply_add_to_node_vars!(du, inv_weight_left, (f_num - f_node),
205
equations, dg, 1, l, element)
206
207
# surface at +x
208
u_node = get_node_vars(u, equations, dg, nnodes(dg), l, element)
209
# compute internal flux in normal direction on side 2
210
outward_direction = get_surface_normal(normal_directions, l, 2, element)
211
f_node = flux(u_node, outward_direction, equations)
212
f_num = get_node_vars(surface_flux_values, equations, dg, l, 2, element)
213
multiply_add_to_node_vars!(du, inv_weight_right, (f_num - f_node),
214
equations, dg, nnodes(dg), l, element)
215
216
# surface at -y
217
u_node = get_node_vars(u, equations, dg, l, 1, element)
218
# compute internal flux in normal direction on side 1
219
outward_direction = get_surface_normal(normal_directions, l, 1, element)
220
f_node = flux(u_node, outward_direction, equations)
221
f_num = get_node_vars(surface_flux_values, equations, dg, l, 1, element)
222
multiply_add_to_node_vars!(du, inv_weight_left, (f_num - f_node),
223
equations, dg, l, 1, element)
224
225
# surface at +y
226
u_node = get_node_vars(u, equations, dg, l, nnodes(dg), element)
227
# compute internal flux in normal direction on side 3
228
outward_direction = get_surface_normal(normal_directions, l, 3, element)
229
f_node = flux(u_node, outward_direction, equations)
230
f_num = get_node_vars(surface_flux_values, equations, dg, l, 3, element)
231
multiply_add_to_node_vars!(du, inv_weight_right, (f_num - f_node),
232
equations, dg, l, nnodes(dg), element)
233
end
234
end
235
236
return nothing
237
end
238
239
# AnalysisCallback
240
function integrate_via_indices(func::Func, u,
241
mesh::UnstructuredMesh2D, equations,
242
dg::FDSBP, cache, args...; normalize = true) where {Func}
243
# TODO: FD. This is rather inefficient right now and allocates...
244
weights = diag(SummationByPartsOperators.mass_matrix(dg.basis))
245
246
# Initialize integral with zeros of the right shape
247
integral = zero(func(u, 1, 1, 1, equations, dg, args...))
248
total_volume = zero(real(mesh))
249
250
# Use quadrature to numerically integrate over entire domain
251
@batch reduction=((+, integral), (+, total_volume)) for element in eachelement(dg,
252
cache)
253
for j in eachnode(dg), i in eachnode(dg)
254
volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element]))
255
integral += volume_jacobian * weights[i] * weights[j] *
256
func(u, i, j, element, equations, dg, args...)
257
total_volume += volume_jacobian * weights[i] * weights[j]
258
end
259
end
260
261
# Normalize with total volume
262
if normalize
263
integral = integral / total_volume
264
end
265
266
return integral
267
end
268
269
function calc_error_norms(func, u, t, analyzer,
270
mesh::UnstructuredMesh2D, equations, initial_condition,
271
dg::FDSBP, cache, cache_analysis)
272
# TODO: FD. This is rather inefficient right now and allocates...
273
weights = diag(SummationByPartsOperators.mass_matrix(dg.basis))
274
@unpack node_coordinates, inverse_jacobian = cache.elements
275
276
# Set up data structures
277
l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations))
278
linf_error = copy(l2_error)
279
total_volume = zero(real(mesh))
280
281
# Iterate over all elements for error calculations
282
for element in eachelement(dg, cache)
283
for j in eachnode(analyzer), i in eachnode(analyzer)
284
volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element]))
285
u_exact = initial_condition(get_node_coords(node_coordinates, equations, dg,
286
i, j, element), t, equations)
287
diff = func(u_exact, equations) -
288
func(get_node_vars(u, equations, dg, i, j, element), equations)
289
l2_error += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian)
290
linf_error = @. max(linf_error, abs(diff))
291
total_volume += weights[i] * weights[j] * volume_jacobian
292
end
293
end
294
295
# For L2 error, divide by total volume
296
l2_error = @. sqrt(l2_error / total_volume)
297
298
return l2_error, linf_error
299
end
300
end # @muladd
301
302