Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/callbacks_step/analysis_dg2d.jl
2055 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
function create_cache_analysis(analyzer, mesh::TreeMesh{2},
9
equations, dg::DG, cache,
10
RealT, uEltype)
11
12
# pre-allocate buffers
13
# We use `StrideArray`s here since these buffers are used in performance-critical
14
# places and the additional information passed to the compiler makes them faster
15
# than native `Array`s.
16
u_local = StrideArray(undef, uEltype,
17
StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),
18
StaticInt(nnodes(analyzer)))
19
u_tmp1 = StrideArray(undef, uEltype,
20
StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),
21
StaticInt(nnodes(dg)))
22
x_local = StrideArray(undef, RealT,
23
StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),
24
StaticInt(nnodes(analyzer)))
25
x_tmp1 = StrideArray(undef, RealT,
26
StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),
27
StaticInt(nnodes(dg)))
28
29
return (; u_local, u_tmp1, x_local, x_tmp1)
30
end
31
32
# Specialized cache for P4estMesh to allow for different ambient dimension from mesh dimension
33
function create_cache_analysis(analyzer,
34
mesh::Union{P4estMesh{2, NDIMS_AMBIENT},
35
P4estMeshView{2, NDIMS_AMBIENT}},
36
equations, dg::DG, cache,
37
RealT, uEltype) where {NDIMS_AMBIENT}
38
39
# pre-allocate buffers
40
# We use `StrideArray`s here since these buffers are used in performance-critical
41
# places and the additional information passed to the compiler makes them faster
42
# than native `Array`s.
43
u_local = StrideArray(undef, uEltype,
44
StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),
45
StaticInt(nnodes(analyzer)))
46
u_tmp1 = StrideArray(undef, uEltype,
47
StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),
48
StaticInt(nnodes(dg)))
49
x_local = StrideArray(undef, RealT,
50
StaticInt(NDIMS_AMBIENT), StaticInt(nnodes(analyzer)),
51
StaticInt(nnodes(analyzer)))
52
x_tmp1 = StrideArray(undef, RealT,
53
StaticInt(NDIMS_AMBIENT), StaticInt(nnodes(analyzer)),
54
StaticInt(nnodes(dg)))
55
jacobian_local = StrideArray(undef, RealT,
56
StaticInt(nnodes(analyzer)),
57
StaticInt(nnodes(analyzer)))
58
jacobian_tmp1 = StrideArray(undef, RealT,
59
StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))
60
61
return (; u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1)
62
end
63
64
function create_cache_analysis(analyzer,
65
mesh::Union{StructuredMesh{2}, StructuredMeshView{2},
66
UnstructuredMesh2D, T8codeMesh{2}},
67
equations, dg::DG, cache,
68
RealT, uEltype)
69
70
# pre-allocate buffers
71
# We use `StrideArray`s here since these buffers are used in performance-critical
72
# places and the additional information passed to the compiler makes them faster
73
# than native `Array`s.
74
u_local = StrideArray(undef, uEltype,
75
StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),
76
StaticInt(nnodes(analyzer)))
77
u_tmp1 = StrideArray(undef, uEltype,
78
StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)),
79
StaticInt(nnodes(dg)))
80
x_local = StrideArray(undef, RealT,
81
StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),
82
StaticInt(nnodes(analyzer)))
83
x_tmp1 = StrideArray(undef, RealT,
84
StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)),
85
StaticInt(nnodes(dg)))
86
jacobian_local = StrideArray(undef, RealT,
87
StaticInt(nnodes(analyzer)),
88
StaticInt(nnodes(analyzer)))
89
jacobian_tmp1 = StrideArray(undef, RealT,
90
StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)))
91
92
return (; u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1)
93
end
94
95
function calc_error_norms(func, u, t, analyzer,
96
mesh::TreeMesh{2}, equations, initial_condition,
97
dg::DGSEM, cache, cache_analysis)
98
@unpack vandermonde, weights = analyzer
99
@unpack node_coordinates = cache.elements
100
@unpack u_local, u_tmp1, x_local, x_tmp1 = cache_analysis
101
102
# Set up data structures
103
l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations))
104
linf_error = copy(l2_error)
105
106
# Iterate over all elements for error calculations
107
# Accumulate L2 error on the element first so that the order of summation is the
108
# same as in the parallel case to ensure exact equality. This facilitates easier parallel
109
# development and debugging (see
110
# https://github.com/trixi-framework/Trixi.jl/pull/850#pullrequestreview-757463943 for details).
111
for element in eachelement(dg, cache)
112
# Set up data structures for local element L2 error
113
l2_error_local = zero(l2_error)
114
115
# Interpolate solution and node locations to analysis nodes
116
multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1)
117
multiply_dimensionwise!(x_local, vandermonde,
118
view(node_coordinates, :, :, :, element), x_tmp1)
119
120
# Calculate errors at each analysis node
121
volume_jacobian_ = volume_jacobian(element, mesh, cache)
122
123
for j in eachnode(analyzer), i in eachnode(analyzer)
124
u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j),
125
t, equations)
126
diff = func(u_exact, equations) -
127
func(get_node_vars(u_local, equations, dg, i, j), equations)
128
l2_error_local += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian_)
129
linf_error = @. max(linf_error, abs(diff))
130
end
131
l2_error += l2_error_local
132
end
133
134
# For L2 error, divide by total volume
135
total_volume_ = total_volume(mesh)
136
l2_error = @. sqrt(l2_error / total_volume_)
137
138
return l2_error, linf_error
139
end
140
141
function calc_error_norms(func, u, t, analyzer,
142
mesh::Union{StructuredMesh{2}, StructuredMeshView{2},
143
UnstructuredMesh2D,
144
P4estMesh{2}, P4estMeshView{2},
145
T8codeMesh{2}},
146
equations,
147
initial_condition, dg::DGSEM, cache, cache_analysis)
148
@unpack vandermonde, weights = analyzer
149
@unpack node_coordinates, inverse_jacobian = cache.elements
150
@unpack u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1 = cache_analysis
151
152
# Set up data structures
153
l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations))
154
linf_error = copy(l2_error)
155
total_volume = zero(real(mesh))
156
157
# Iterate over all elements for error calculations
158
for element in eachelement(dg, cache)
159
# Interpolate solution and node locations to analysis nodes
160
multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1)
161
multiply_dimensionwise!(x_local, vandermonde,
162
view(node_coordinates, :, :, :, element), x_tmp1)
163
multiply_scalar_dimensionwise!(jacobian_local, vandermonde,
164
inv.(view(inverse_jacobian, :, :, element)),
165
jacobian_tmp1)
166
167
# Calculate errors at each analysis node
168
for j in eachnode(analyzer), i in eachnode(analyzer)
169
u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j),
170
t, equations)
171
diff = func(u_exact, equations) -
172
func(get_node_vars(u_local, equations, dg, i, j), equations)
173
# We take absolute value as we need the Jacobian here for the volume calculation
174
abs_jacobian_local_ij = abs(jacobian_local[i, j])
175
176
l2_error += diff .^ 2 * (weights[i] * weights[j] * abs_jacobian_local_ij)
177
linf_error = @. max(linf_error, abs(diff))
178
total_volume += weights[i] * weights[j] * abs_jacobian_local_ij
179
end
180
end
181
182
# For L2 error, divide by total volume
183
l2_error = @. sqrt(l2_error / total_volume)
184
185
return l2_error, linf_error
186
end
187
188
function integrate_via_indices(func::Func, u,
189
mesh::TreeMesh{2}, equations, dg::DGSEM, cache,
190
args...; normalize = true) where {Func}
191
@unpack weights = dg.basis
192
193
# Initialize integral with zeros of the right shape
194
integral = zero(func(u, 1, 1, 1, equations, dg, args...))
195
196
# Use quadrature to numerically integrate over entire domain
197
@batch reduction=(+, integral) for element in eachelement(dg, cache)
198
volume_jacobian_ = volume_jacobian(element, mesh, cache)
199
for j in eachnode(dg), i in eachnode(dg)
200
integral += volume_jacobian_ * weights[i] * weights[j] *
201
func(u, i, j, element, equations, dg, args...)
202
end
203
end
204
205
# Normalize with total volume
206
if normalize
207
integral = integral / total_volume(mesh)
208
end
209
210
return integral
211
end
212
213
function integrate_via_indices(func::Func, u,
214
mesh::Union{StructuredMesh{2}, StructuredMeshView{2},
215
UnstructuredMesh2D, P4estMesh{2},
216
T8codeMesh{2}},
217
equations,
218
dg::DGSEM, cache, args...; normalize = true) where {Func}
219
@unpack weights = dg.basis
220
221
# Initialize integral with zeros of the right shape
222
integral = zero(func(u, 1, 1, 1, equations, dg, args...))
223
total_volume = zero(real(mesh))
224
225
# Use quadrature to numerically integrate over entire domain
226
@batch reduction=((+, integral), (+, total_volume)) for element in eachelement(dg,
227
cache)
228
for j in eachnode(dg), i in eachnode(dg)
229
volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element]))
230
integral += volume_jacobian * weights[i] * weights[j] *
231
func(u, i, j, element, equations, dg, args...)
232
total_volume += volume_jacobian * weights[i] * weights[j]
233
end
234
end
235
236
# Normalize with total volume
237
if normalize
238
integral = integral / total_volume
239
end
240
241
return integral
242
end
243
244
function integrate(func::Func, u,
245
mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2},
246
UnstructuredMesh2D, P4estMesh{2}, P4estMeshView{2},
247
T8codeMesh{2}},
248
equations, dg::DG, cache; normalize = true) where {Func}
249
integrate_via_indices(u, mesh, equations, dg, cache;
250
normalize = normalize) do u, i, j, element, equations, dg
251
u_local = get_node_vars(u, equations, dg, i, j, element)
252
return func(u_local, equations)
253
end
254
end
255
256
function integrate(func::Func, u,
257
mesh::Union{TreeMesh{2}, P4estMesh{2}},
258
equations, equations_parabolic,
259
dg::DGSEM,
260
cache, cache_parabolic; normalize = true) where {Func}
261
gradients_x, gradients_y = cache_parabolic.viscous_container.gradients
262
integrate_via_indices(u, mesh, equations, dg, cache;
263
normalize = normalize) do u, i, j, element, equations, dg
264
u_local = get_node_vars(u, equations, dg, i, j, element)
265
gradients_1_local = get_node_vars(gradients_x, equations_parabolic, dg, i, j,
266
element)
267
gradients_2_local = get_node_vars(gradients_y, equations_parabolic, dg, i, j,
268
element)
269
return func(u_local, (gradients_1_local, gradients_2_local),
270
equations_parabolic)
271
end
272
end
273
274
function analyze(::typeof(entropy_timederivative), du, u, t,
275
mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2},
276
UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}},
277
equations, dg::DG, cache)
278
# Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ
279
integrate_via_indices(u, mesh, equations, dg, cache,
280
du) do u, i, j, element, equations, dg, du
281
u_node = get_node_vars(u, equations, dg, i, j, element)
282
du_node = get_node_vars(du, equations, dg, i, j, element)
283
dot(cons2entropy(u_node, equations), du_node)
284
end
285
end
286
287
function analyze(::Val{:l2_divb}, du, u, t,
288
mesh::TreeMesh{2},
289
equations, dg::DGSEM, cache)
290
integrate_via_indices(u, mesh, equations, dg, cache, cache,
291
dg.basis.derivative_matrix) do u, i, j, element, equations,
292
dg, cache, derivative_matrix
293
divb = zero(eltype(u))
294
for k in eachnode(dg)
295
u_kj = get_node_vars(u, equations, dg, k, j, element)
296
u_ik = get_node_vars(u, equations, dg, i, k, element)
297
298
B1_kj, _, _ = magnetic_field(u_kj, equations)
299
_, B2_ik, _ = magnetic_field(u_ik, equations)
300
301
divb += (derivative_matrix[i, k] * B1_kj +
302
derivative_matrix[j, k] * B2_ik)
303
end
304
divb *= cache.elements.inverse_jacobian[element]
305
divb^2
306
end |> sqrt
307
end
308
309
function analyze(::Val{:l2_divb}, du, u, t,
310
mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2},
311
T8codeMesh{2}},
312
equations, dg::DGSEM, cache)
313
@unpack contravariant_vectors = cache.elements
314
integrate_via_indices(u, mesh, equations, dg, cache, cache,
315
dg.basis.derivative_matrix) do u, i, j, element, equations,
316
dg, cache, derivative_matrix
317
divb = zero(eltype(u))
318
# Get the contravariant vectors Ja^1 and Ja^2
319
Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, element)
320
Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, element)
321
# Compute the transformed divergence
322
for k in eachnode(dg)
323
u_kj = get_node_vars(u, equations, dg, k, j, element)
324
u_ik = get_node_vars(u, equations, dg, i, k, element)
325
326
B1_kj, B2_kj, _ = magnetic_field(u_kj, equations)
327
B1_ik, B2_ik, _ = magnetic_field(u_ik, equations)
328
329
divb += (derivative_matrix[i, k] *
330
(Ja11 * B1_kj + Ja12 * B2_kj) +
331
derivative_matrix[j, k] *
332
(Ja21 * B1_ik + Ja22 * B2_ik))
333
end
334
divb *= cache.elements.inverse_jacobian[i, j, element]
335
divb^2
336
end |> sqrt
337
end
338
339
function analyze(::Val{:linf_divb}, du, u, t,
340
mesh::TreeMesh{2},
341
equations, dg::DGSEM, cache)
342
@unpack derivative_matrix, weights = dg.basis
343
344
# integrate over all elements to get the divergence-free condition errors
345
linf_divb = zero(eltype(u))
346
@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)
347
for j in eachnode(dg), i in eachnode(dg)
348
divb = zero(eltype(u))
349
for k in eachnode(dg)
350
u_kj = get_node_vars(u, equations, dg, k, j, element)
351
u_ik = get_node_vars(u, equations, dg, i, k, element)
352
353
B1_kj, _, _ = magnetic_field(u_kj, equations)
354
_, B2_ik, _ = magnetic_field(u_ik, equations)
355
356
divb += (derivative_matrix[i, k] * B1_kj +
357
derivative_matrix[j, k] * B2_ik)
358
end
359
divb *= cache.elements.inverse_jacobian[element]
360
linf_divb = max(linf_divb, abs(divb))
361
end
362
end
363
364
return linf_divb
365
end
366
367
function analyze(::Val{:linf_divb}, du, u, t,
368
mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2},
369
T8codeMesh{2}},
370
equations, dg::DGSEM, cache)
371
@unpack derivative_matrix, weights = dg.basis
372
@unpack contravariant_vectors = cache.elements
373
374
# integrate over all elements to get the divergence-free condition errors
375
linf_divb = zero(eltype(u))
376
@batch reduction=(max, linf_divb) for element in eachelement(dg, cache)
377
for j in eachnode(dg), i in eachnode(dg)
378
divb = zero(eltype(u))
379
# Get the contravariant vectors Ja^1 and Ja^2
380
Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors,
381
i, j, element)
382
Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors,
383
i, j, element)
384
# Compute the transformed divergence
385
for k in eachnode(dg)
386
u_kj = get_node_vars(u, equations, dg, k, j, element)
387
u_ik = get_node_vars(u, equations, dg, i, k, element)
388
389
B1_kj, B2_kj, _ = magnetic_field(u_kj, equations)
390
B1_ik, B2_ik, _ = magnetic_field(u_ik, equations)
391
392
divb += (derivative_matrix[i, k] *
393
(Ja11 * B1_kj + Ja12 * B2_kj) +
394
derivative_matrix[j, k] *
395
(Ja21 * B1_ik + Ja22 * B2_ik))
396
end
397
divb *= cache.elements.inverse_jacobian[i, j, element]
398
linf_divb = max(linf_divb, abs(divb))
399
end
400
end
401
if mpi_isparallel()
402
# Base.max instead of max needed, see comment in src/auxiliary/math.jl
403
linf_divb = MPI.Allreduce!(Ref(linf_divb), Base.max, mpi_comm())[]
404
end
405
406
return linf_divb
407
end
408
end # @muladd
409
410