Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/callbacks_step/amr_dg3d.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
# Refine elements in the DG solver based on a list of cell_ids that should be refined.
9
# On `P4estMesh`, if an element refines the solution scaled by the Jacobian `J*u` is interpolated
10
# from the parent element into the eight children elements. The solution on each child
11
# element is then recovered by dividing by the new element Jacobians.
12
function refine!(u_ode::AbstractVector, adaptor, mesh::Union{TreeMesh{3}, P4estMesh{3}},
13
equations, dg::DGSEM, cache, elements_to_refine)
14
# Return early if there is nothing to do
15
if isempty(elements_to_refine)
16
if mpi_isparallel()
17
# MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do
18
# locally (there still might be other MPI ranks that have refined elements)
19
reinitialize_containers!(mesh, equations, dg, cache)
20
end
21
return
22
end
23
24
# Determine for each existing element whether it needs to be refined
25
needs_refinement = falses(nelements(dg, cache))
26
needs_refinement[elements_to_refine] .= true
27
28
# Retain current solution data
29
old_n_elements = nelements(dg, cache)
30
old_u_ode = copy(u_ode)
31
old_inverse_jacobian = copy(cache.elements.inverse_jacobian)
32
# OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed
33
GC.@preserve old_u_ode old_inverse_jacobian begin
34
old_u = wrap_array(old_u_ode, mesh, equations, dg, cache)
35
36
if mesh isa P4estMesh
37
# Loop over all elements in old container and scale the old solution by the Jacobian
38
# prior to projection
39
for old_element_id in 1:old_n_elements
40
for v in eachvariable(equations)
41
old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./
42
old_inverse_jacobian[..,
43
old_element_id])
44
end
45
end
46
end
47
48
reinitialize_containers!(mesh, equations, dg, cache)
49
50
resize!(u_ode,
51
nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache))
52
u = wrap_array(u_ode, mesh, equations, dg, cache)
53
54
# Loop over all elements in old container and either copy them or refine them
55
u_tmp1 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg),
56
nnodes(dg), nnodes(dg))
57
u_tmp2 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg),
58
nnodes(dg), nnodes(dg))
59
element_id = 1
60
for old_element_id in 1:old_n_elements
61
if needs_refinement[old_element_id]
62
# Refine element and store solution directly in new data structure
63
refine_element!(u, element_id, old_u, old_element_id, adaptor,
64
equations, dg, u_tmp1, u_tmp2)
65
66
if mesh isa P4estMesh
67
# Before `element_id` is incremented, divide by the new Jacobians on each
68
# child element and save the result
69
for m in 0:7 # loop over the children
70
for v in eachvariable(equations)
71
u[v, .., element_id + m] .*= (0.125f0 .*
72
cache.elements.inverse_jacobian[..,
73
element_id + m])
74
end
75
end
76
end
77
78
# Increment `element_id` on the refined mesh with the number
79
# of children, i.e., 8 in 3D
80
element_id += 2^ndims(mesh)
81
else
82
if mesh isa P4estMesh
83
# Copy old element data to new element container and remove Jacobian scaling
84
for v in eachvariable(equations)
85
u[v, .., element_id] .= (old_u[v, .., old_element_id] .*
86
old_inverse_jacobian[..,
87
old_element_id])
88
end
89
else # isa TreeMesh
90
@views u[:, .., element_id] .= old_u[:, .., old_element_id]
91
end
92
# No refinement occurred, so increment `element_id` on the new mesh by one
93
element_id += 1
94
end
95
end
96
# If everything is correct, we should have processed all elements.
97
# Depending on whether the last element processed above had to be refined or not,
98
# the counter `element_id` can have two different values at the end.
99
@assert element_id ==
100
nelements(dg, cache) +
101
1||element_id == nelements(dg, cache) + 2^ndims(mesh) "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))"
102
end # GC.@preserve old_u_ode old_inverse_jacobian
103
104
# Sanity check
105
if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0
106
@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")
107
end
108
109
return nothing
110
end
111
112
# TODO: Taal compare performance of different implementations
113
# Refine solution data u for an element, using L2 projection (interpolation)
114
function refine_element!(u::AbstractArray{<:Any, 5}, element_id,
115
old_u, old_element_id,
116
adaptor::LobattoLegendreAdaptorL2, equations, dg,
117
u_tmp1, u_tmp2)
118
@unpack forward_upper, forward_lower = adaptor
119
120
# Store new element ids
121
bottom_lower_left_id = element_id
122
bottom_lower_right_id = element_id + 1
123
bottom_upper_left_id = element_id + 2
124
bottom_upper_right_id = element_id + 3
125
top_lower_left_id = element_id + 4
126
top_lower_right_id = element_id + 5
127
top_upper_left_id = element_id + 6
128
top_upper_right_id = element_id + 7
129
130
@boundscheck begin
131
@assert old_element_id >= 1
132
@assert size(old_u, 1) == nvariables(equations)
133
@assert size(old_u, 2) == nnodes(dg)
134
@assert size(old_u, 3) == nnodes(dg)
135
@assert size(old_u, 4) == nnodes(dg)
136
@assert size(old_u, 5) >= old_element_id
137
@assert element_id >= 1
138
@assert size(u, 1) == nvariables(equations)
139
@assert size(u, 2) == nnodes(dg)
140
@assert size(u, 3) == nnodes(dg)
141
@assert size(u, 4) == nnodes(dg)
142
@assert size(u, 5) >= element_id + 7
143
end
144
145
# Interpolate to bottom lower left element
146
multiply_dimensionwise!(view(u, :, :, :, :, bottom_lower_left_id), forward_lower,
147
forward_lower, forward_lower,
148
view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2)
149
150
# Interpolate to bottom lower right element
151
multiply_dimensionwise!(view(u, :, :, :, :, bottom_lower_right_id), forward_upper,
152
forward_lower, forward_lower,
153
view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2)
154
155
# Interpolate to bottom upper left element
156
multiply_dimensionwise!(view(u, :, :, :, :, bottom_upper_left_id), forward_lower,
157
forward_upper, forward_lower,
158
view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2)
159
160
# Interpolate to bottom upper right element
161
multiply_dimensionwise!(view(u, :, :, :, :, bottom_upper_right_id), forward_upper,
162
forward_upper, forward_lower,
163
view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2)
164
165
# Interpolate to top lower left element
166
multiply_dimensionwise!(view(u, :, :, :, :, top_lower_left_id), forward_lower,
167
forward_lower, forward_upper,
168
view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2)
169
170
# Interpolate to top lower right element
171
multiply_dimensionwise!(view(u, :, :, :, :, top_lower_right_id), forward_upper,
172
forward_lower, forward_upper,
173
view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2)
174
175
# Interpolate to top upper left element
176
multiply_dimensionwise!(view(u, :, :, :, :, top_upper_left_id), forward_lower,
177
forward_upper, forward_upper,
178
view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2)
179
180
# Interpolate to top upper right element
181
multiply_dimensionwise!(view(u, :, :, :, :, top_upper_right_id), forward_upper,
182
forward_upper, forward_upper,
183
view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2)
184
185
return nothing
186
end
187
188
# Coarsen elements in the DG solver based on a list of cell_ids that should be removed.
189
# On `P4estMesh`, if an element coarsens the solution scaled by the Jacobian `J*u` is projected
190
# from the eight children elements back onto the parent element. The solution on the parent
191
# element is then recovered by dividing by the new element Jacobian.
192
function coarsen!(u_ode::AbstractVector, adaptor,
193
mesh::Union{TreeMesh{3}, P4estMesh{3}},
194
equations, dg::DGSEM, cache, elements_to_remove)
195
# Return early if there is nothing to do
196
if isempty(elements_to_remove)
197
if mpi_isparallel()
198
# MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do
199
# locally (there still might be other MPI ranks that have coarsened elements)
200
reinitialize_containers!(mesh, equations, dg, cache)
201
end
202
return
203
end
204
205
# Determine for each old element whether it needs to be removed
206
to_be_removed = falses(nelements(dg, cache))
207
to_be_removed[elements_to_remove] .= true
208
209
# Retain current solution data and Jacobians
210
old_n_elements = nelements(dg, cache)
211
old_u_ode = copy(u_ode)
212
old_inverse_jacobian = copy(cache.elements.inverse_jacobian)
213
# OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed
214
GC.@preserve old_u_ode old_inverse_jacobian begin
215
old_u = wrap_array(old_u_ode, mesh, equations, dg, cache)
216
217
if mesh isa P4estMesh
218
# Loop over all elements in old container and scale the old solution by the Jacobian
219
# prior to projection
220
for old_element_id in 1:old_n_elements
221
for v in eachvariable(equations)
222
old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./
223
old_inverse_jacobian[..,
224
old_element_id])
225
end
226
end
227
end
228
229
reinitialize_containers!(mesh, equations, dg, cache)
230
231
resize!(u_ode,
232
nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache))
233
u = wrap_array(u_ode, mesh, equations, dg, cache)
234
235
# Loop over all elements in old container and either copy them or coarsen them
236
u_tmp1 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg),
237
nnodes(dg), nnodes(dg))
238
u_tmp2 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg),
239
nnodes(dg), nnodes(dg))
240
skip = 0
241
element_id = 1
242
for old_element_id in 1:old_n_elements
243
# If skip is non-zero, we just coarsened 2^ndims elements and need to omit the following elements
244
if skip > 0
245
skip -= 1
246
continue
247
end
248
249
if to_be_removed[old_element_id]
250
# If an element is to be removed, sanity check if the following elements
251
# are also marked - otherwise there would be an error in the way the
252
# cells/elements are sorted
253
@assert all(to_be_removed[old_element_id:(old_element_id + 2^ndims(mesh) - 1)]) "bad cell/element order"
254
255
# Coarsen elements and store solution directly in new data structure
256
coarsen_elements!(u, element_id, old_u, old_element_id, adaptor,
257
equations, dg, u_tmp1, u_tmp2)
258
259
if mesh isa P4estMesh
260
# Before `element_id` is incremented, divide by the new Jacobian and save
261
# the result in the parent element
262
for v in eachvariable(equations)
263
u[v, .., element_id] .*= (8 .*
264
cache.elements.inverse_jacobian[..,
265
element_id])
266
end
267
end
268
269
# Increment `element_id` on the coarsened mesh by one and `skip` = 7 in 3D
270
# because 8 children elements become 1 parent element
271
element_id += 1
272
skip = 2^ndims(mesh) - 1
273
else
274
if mesh isa P4estMesh
275
# Copy old element data to new element container and remove Jacobian scaling
276
for v in eachvariable(equations)
277
u[v, .., element_id] .= (old_u[v, .., old_element_id] .*
278
old_inverse_jacobian[..,
279
old_element_id])
280
end
281
else # isa TreeMesh
282
@views u[:, .., element_id] .= old_u[:, .., old_element_id]
283
end
284
# No coarsening occurred, so increment `element_id` on the new mesh by one
285
element_id += 1
286
end
287
end
288
# If everything is correct, we should have processed all elements.
289
@assert element_id==nelements(dg, cache) + 1 "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))"
290
end # GC.@preserve old_u_ode old_inverse_jacobian
291
292
# Sanity check
293
if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0
294
@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")
295
end
296
297
return nothing
298
end
299
300
# TODO: Taal compare performance of different implementations
301
# Coarsen solution data u for four elements, using L2 projection
302
function coarsen_elements!(u::AbstractArray{<:Any, 5}, element_id,
303
old_u, old_element_id,
304
adaptor::LobattoLegendreAdaptorL2, equations, dg,
305
u_tmp1, u_tmp2)
306
@unpack reverse_upper, reverse_lower = adaptor
307
308
# Store old element ids
309
bottom_lower_left_id = old_element_id
310
bottom_lower_right_id = old_element_id + 1
311
bottom_upper_left_id = old_element_id + 2
312
bottom_upper_right_id = old_element_id + 3
313
top_lower_left_id = old_element_id + 4
314
top_lower_right_id = old_element_id + 5
315
top_upper_left_id = old_element_id + 6
316
top_upper_right_id = old_element_id + 7
317
318
@boundscheck begin
319
@assert old_element_id >= 1
320
@assert size(old_u, 1) == nvariables(equations)
321
@assert size(old_u, 2) == nnodes(dg)
322
@assert size(old_u, 3) == nnodes(dg)
323
@assert size(old_u, 4) == nnodes(dg)
324
@assert size(old_u, 5) >= old_element_id + 7
325
@assert element_id >= 1
326
@assert size(u, 1) == nvariables(equations)
327
@assert size(u, 2) == nnodes(dg)
328
@assert size(u, 3) == nnodes(dg)
329
@assert size(u, 4) == nnodes(dg)
330
@assert size(u, 5) >= element_id
331
end
332
333
# Project from bottom lower left element
334
multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_lower,
335
reverse_lower, reverse_lower,
336
view(old_u, :, :, :, :, bottom_lower_left_id), u_tmp1,
337
u_tmp2)
338
339
# Project from bottom lower right element_variables
340
add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_upper,
341
reverse_lower, reverse_lower,
342
view(old_u, :, :, :, :, bottom_lower_right_id), u_tmp1,
343
u_tmp2)
344
345
# Project from bottom upper left element
346
add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_lower,
347
reverse_upper, reverse_lower,
348
view(old_u, :, :, :, :, bottom_upper_left_id), u_tmp1,
349
u_tmp2)
350
351
# Project from bottom upper right element
352
add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_upper,
353
reverse_upper, reverse_lower,
354
view(old_u, :, :, :, :, bottom_upper_right_id), u_tmp1,
355
u_tmp2)
356
357
# Project from top lower left element
358
add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_lower,
359
reverse_lower, reverse_upper,
360
view(old_u, :, :, :, :, top_lower_left_id), u_tmp1,
361
u_tmp2)
362
363
# Project from top lower right element
364
add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_upper,
365
reverse_lower, reverse_upper,
366
view(old_u, :, :, :, :, top_lower_right_id), u_tmp1,
367
u_tmp2)
368
369
# Project from top upper left element
370
add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_lower,
371
reverse_upper, reverse_upper,
372
view(old_u, :, :, :, :, top_upper_left_id), u_tmp1,
373
u_tmp2)
374
375
# Project from top upper right element
376
add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_upper,
377
reverse_upper, reverse_upper,
378
view(old_u, :, :, :, :, top_upper_right_id), u_tmp1,
379
u_tmp2)
380
381
return nothing
382
end
383
384
# Coarsen and refine elements in the DG solver based on a difference list.
385
function adapt!(u_ode::AbstractVector, adaptor, mesh::T8codeMesh{3}, equations,
386
dg::DGSEM, cache, difference)
387
388
# Return early if there is nothing to do.
389
if !any(difference .!= 0)
390
if mpi_isparallel()
391
# MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do
392
# locally (there still might be other MPI ranks that have refined elements)
393
reinitialize_containers!(mesh, equations, dg, cache)
394
end
395
return
396
end
397
398
# Number of (local) cells/elements.
399
old_nelems = nelements(dg, cache)
400
new_nelems = ncells(mesh)
401
402
# Local element indices.
403
old_index = 1
404
new_index = 1
405
406
# Note: This is only true for `hexs`.
407
T8_CHILDREN = 8
408
409
# Retain current solution and inverse Jacobian data.
410
old_u_ode = copy(u_ode)
411
old_inverse_jacobian = copy(cache.elements.inverse_jacobian)
412
413
# OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed
414
GC.@preserve old_u_ode begin
415
old_u = wrap_array(old_u_ode, mesh, equations, dg, cache)
416
417
# Loop over all elements in old container and scale the old solution by the Jacobian
418
# prior to interpolation or projection
419
for old_element_id in 1:old_nelems
420
for v in eachvariable(equations)
421
old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./
422
old_inverse_jacobian[..,
423
old_element_id])
424
end
425
end
426
427
reinitialize_containers!(mesh, equations, dg, cache)
428
429
resize!(u_ode, nvariables(equations) * ndofs(mesh, dg, cache))
430
u = wrap_array(u_ode, mesh, equations, dg, cache)
431
432
u_tmp1 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg),
433
nnodes(dg), nnodes(dg))
434
u_tmp2 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg),
435
nnodes(dg), nnodes(dg))
436
437
while old_index <= old_nelems && new_index <= new_nelems
438
if difference[old_index] > 0 # Refine.
439
440
# Refine element and store solution directly in new data structure.
441
refine_element!(u, new_index, old_u, old_index, adaptor, equations, dg,
442
u_tmp1, u_tmp2)
443
444
# Before indices are incremented divide by the new Jacobians on each
445
# child element and save the result
446
for m in 0:7 # loop over the children
447
for v in eachvariable(equations)
448
u[v, .., new_index + m] .*= (0.125f0 .*
449
cache.elements.inverse_jacobian[..,
450
new_index + m])
451
end
452
end
453
454
# Increment `old_index` on the original mesh and the `new_index`
455
# on the refined mesh with the number of children, i.e., T8_CHILDREN = 8
456
old_index += 1
457
new_index += T8_CHILDREN
458
459
elseif difference[old_index] < 0 # Coarsen.
460
461
# If an element is to be removed, sanity check if the following elements
462
# are also marked - otherwise there would be an error in the way the
463
# cells/elements are sorted.
464
@assert all(difference[old_index:(old_index + T8_CHILDREN - 1)] .< 0) "bad cell/element order"
465
466
# Coarsen elements and store solution directly in new data structure.
467
coarsen_elements!(u, new_index, old_u, old_index, adaptor, equations,
468
dg, u_tmp1, u_tmp2)
469
470
# Before the indices are incremented divide by the new Jacobian and save
471
# the result again in the parent element
472
for v in eachvariable(equations)
473
u[v, .., new_index] .*= (8 .* cache.elements.inverse_jacobian[..,
474
new_index])
475
end
476
477
# Increment `old_index` on the original mesh with the number of children
478
# (T8_CHILDREN = 8 in 3D) and the `new_index` by one for the single
479
# coarsened element
480
old_index += T8_CHILDREN
481
new_index += 1
482
483
else # No changes.
484
485
# Copy old element data to new element container and remove Jacobian scaling
486
for v in eachvariable(equations)
487
u[v, .., new_index] .= (old_u[v, .., old_index] .*
488
old_inverse_jacobian[.., old_index])
489
end
490
491
# No refinement / coarsening occurred, so increment element index
492
# on each mesh by one
493
old_index += 1
494
new_index += 1
495
end
496
end # while
497
end # GC.@preserve old_u_ode old_inverse_jacobian
498
499
return nothing
500
end
501
end # @muladd
502
503