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