Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem_tree/dg_2d_parallel.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
# everything related to a DG semidiscretization in 2D using MPI,
9
# currently limited to Lobatto-Legendre nodes
10
11
# TODO: MPI dimension agnostic
12
mutable struct MPICache{uEltype <: Real}
13
mpi_neighbor_ranks::Vector{Int}
14
mpi_neighbor_interfaces::Vector{Vector{Int}}
15
mpi_neighbor_mortars::Vector{Vector{Int}}
16
mpi_send_buffers::Vector{Vector{uEltype}}
17
mpi_recv_buffers::Vector{Vector{uEltype}}
18
mpi_send_requests::Vector{MPI.Request}
19
mpi_recv_requests::Vector{MPI.Request}
20
n_elements_by_rank::OffsetArray{Int, 1, Array{Int, 1}}
21
n_elements_global::Int
22
first_element_global_id::Int
23
end
24
25
function MPICache(uEltype)
26
# MPI communication "just works" for bitstypes only
27
if !isbitstype(uEltype)
28
throw(ArgumentError("MPICache only supports bitstypes, $uEltype is not a bitstype."))
29
end
30
mpi_neighbor_ranks = Vector{Int}(undef, 0)
31
mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, 0)
32
mpi_neighbor_mortars = Vector{Vector{Int}}(undef, 0)
33
mpi_send_buffers = Vector{Vector{uEltype}}(undef, 0)
34
mpi_recv_buffers = Vector{Vector{uEltype}}(undef, 0)
35
mpi_send_requests = Vector{MPI.Request}(undef, 0)
36
mpi_recv_requests = Vector{MPI.Request}(undef, 0)
37
n_elements_by_rank = OffsetArray(Vector{Int}(undef, 0), 0:-1)
38
n_elements_global = 0
39
first_element_global_id = 0
40
41
return MPICache{uEltype}(mpi_neighbor_ranks, mpi_neighbor_interfaces,
42
mpi_neighbor_mortars,
43
mpi_send_buffers, mpi_recv_buffers,
44
mpi_send_requests, mpi_recv_requests,
45
n_elements_by_rank, n_elements_global,
46
first_element_global_id)
47
end
48
@inline Base.eltype(::MPICache{uEltype}) where {uEltype} = uEltype
49
50
# TODO: MPI dimension agnostic
51
function start_mpi_receive!(mpi_cache::MPICache)
52
for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks)
53
mpi_cache.mpi_recv_requests[index] = MPI.Irecv!(mpi_cache.mpi_recv_buffers[index],
54
rank, rank, mpi_comm())
55
end
56
57
return nothing
58
end
59
60
# TODO: MPI dimension agnostic
61
function start_mpi_send!(mpi_cache::MPICache, mesh, equations, dg, cache)
62
data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1)
63
64
for rank in 1:length(mpi_cache.mpi_neighbor_ranks)
65
send_buffer = mpi_cache.mpi_send_buffers[rank]
66
67
for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[rank])
68
first = (index - 1) * data_size + 1
69
last = (index - 1) * data_size + data_size
70
71
if cache.mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction
72
@views send_buffer[first:last] .= vec(cache.mpi_interfaces.u[2, :, :,
73
interface])
74
else # local element in negative direction
75
@views send_buffer[first:last] .= vec(cache.mpi_interfaces.u[1, :, :,
76
interface])
77
end
78
end
79
80
# Each mortar has a total size of 4 * data_size, set everything to NaN first and overwrite the
81
# parts where local data exists
82
interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[rank]) *
83
data_size
84
mortars_data_size = length(mpi_cache.mpi_neighbor_mortars[rank]) * 4 * data_size
85
send_buffer[(interfaces_data_size + 1):(interfaces_data_size + mortars_data_size)] .= NaN
86
87
for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[rank])
88
# First and last indices in the send buffer for mortar data obtained from local element
89
# in a given position
90
index_base = interfaces_data_size + (index - 1) * 4 * data_size
91
indices = (
92
# first, last for local element in position 1 (lower element)
93
(index_base + 1,
94
index_base + 1 * data_size),
95
# first, last for local element in position 2 (upper element)
96
(index_base + 1 * data_size + 1,
97
index_base + 2 * data_size),
98
# firsts, lasts for local element in position 3 (large element)
99
(index_base + 2 * data_size + 1,
100
index_base + 3 * data_size,
101
index_base + 3 * data_size + 1,
102
index_base + 4 * data_size))
103
104
for position in cache.mpi_mortars.local_neighbor_positions[mortar]
105
# Determine whether the data belongs to the left or right side
106
if cache.mpi_mortars.large_sides[mortar] == 1 # large element on left side
107
if position in (1, 2) # small element
108
leftright = 2
109
else # large element
110
leftright = 1
111
end
112
else # large element on right side
113
if position in (1, 2) # small element
114
leftright = 1
115
else # large element
116
leftright = 2
117
end
118
end
119
# copy data to buffer
120
if position == 1 # lower element
121
first, last = indices[position]
122
@views send_buffer[first:last] .= vec(cache.mpi_mortars.u_lower[leftright,
123
:,
124
:,
125
mortar])
126
elseif position == 2 # upper element
127
first, last = indices[position]
128
@views send_buffer[first:last] .= vec(cache.mpi_mortars.u_upper[leftright,
129
:,
130
:,
131
mortar])
132
else # large element
133
first_lower, last_lower, first_upper, last_upper = indices[position]
134
@views send_buffer[first_lower:last_lower] .= vec(cache.mpi_mortars.u_lower[leftright,
135
:,
136
:,
137
mortar])
138
@views send_buffer[first_upper:last_upper] .= vec(cache.mpi_mortars.u_upper[leftright,
139
:,
140
:,
141
mortar])
142
end
143
end
144
end
145
end
146
147
# Start sending
148
for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks)
149
mpi_cache.mpi_send_requests[index] = MPI.Isend(mpi_cache.mpi_send_buffers[index],
150
rank, mpi_rank(), mpi_comm())
151
end
152
153
return nothing
154
end
155
156
# TODO: MPI dimension agnostic
157
function finish_mpi_send!(mpi_cache::MPICache)
158
return MPI.Waitall(mpi_cache.mpi_send_requests, MPI.Status)
159
end
160
161
# TODO: MPI dimension agnostic
162
function finish_mpi_receive!(mpi_cache::MPICache, mesh, equations, dg, cache)
163
data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1)
164
165
# Start receiving and unpack received data until all communication is finished
166
data = MPI.Waitany(mpi_cache.mpi_recv_requests)
167
while data !== nothing
168
recv_buffer = mpi_cache.mpi_recv_buffers[data]
169
170
for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[data])
171
first = (index - 1) * data_size + 1
172
last = (index - 1) * data_size + data_size
173
174
if cache.mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction
175
@views vec(cache.mpi_interfaces.u[1, :, :, interface]) .= recv_buffer[first:last]
176
else # local element in negative direction
177
@views vec(cache.mpi_interfaces.u[2, :, :, interface]) .= recv_buffer[first:last]
178
end
179
end
180
181
interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[data]) *
182
data_size
183
for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[data])
184
# First and last indices in the receive buffer for mortar data obtained from remote element
185
# in a given position
186
index_base = interfaces_data_size + (index - 1) * 4 * data_size
187
indices = (
188
# first, last for local element in position 1 (lower element)
189
(index_base + 1,
190
index_base + 1 * data_size),
191
# first, last for local element in position 2 (upper element)
192
(index_base + 1 * data_size + 1,
193
index_base + 2 * data_size),
194
# firsts, lasts for local element in position 3 (large element)
195
(index_base + 2 * data_size + 1,
196
index_base + 3 * data_size,
197
index_base + 3 * data_size + 1,
198
index_base + 4 * data_size))
199
200
for position in 1:3
201
# Skip if received data for `pos` is NaN as no real data has been sent for the
202
# corresponding element
203
if isnan(recv_buffer[Base.first(indices[position])])
204
continue
205
end
206
207
# Determine whether the received data belongs to the left or right side
208
if cache.mpi_mortars.large_sides[mortar] == 1 # large element on left side
209
if position in (1, 2) # small element
210
leftright = 2
211
else # large element
212
leftright = 1
213
end
214
else # large element on right side
215
if position in (1, 2) # small element
216
leftright = 1
217
else # large element
218
leftright = 2
219
end
220
end
221
222
if position == 1 # lower element data has been received
223
first, last = indices[position]
224
@views vec(cache.mpi_mortars.u_lower[leftright, :, :, mortar]) .= recv_buffer[first:last]
225
elseif position == 2 # upper element data has been received
226
first, last = indices[position]
227
@views vec(cache.mpi_mortars.u_upper[leftright, :, :, mortar]) .= recv_buffer[first:last]
228
else # large element data has been received
229
first_lower, last_lower, first_upper, last_upper = indices[position]
230
@views vec(cache.mpi_mortars.u_lower[leftright, :, :, mortar]) .= recv_buffer[first_lower:last_lower]
231
@views vec(cache.mpi_mortars.u_upper[leftright, :, :, mortar]) .= recv_buffer[first_upper:last_upper]
232
end
233
end
234
end
235
236
data = MPI.Waitany(mpi_cache.mpi_recv_requests)
237
end
238
239
return nothing
240
end
241
242
# This method is called when a SemidiscretizationHyperbolic is constructed.
243
# It constructs the basic `cache` used throughout the simulation to compute
244
# the RHS etc.
245
function create_cache(mesh::TreeMeshParallel{2}, equations,
246
dg::DG, RealT, ::Type{uEltype}) where {uEltype <: Real}
247
# Get cells for which an element needs to be created (i.e. all leaf cells)
248
leaf_cell_ids = local_leaf_cells(mesh.tree)
249
250
elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype)
251
252
interfaces = init_interfaces(leaf_cell_ids, mesh, elements)
253
254
mpi_interfaces = init_mpi_interfaces(leaf_cell_ids, mesh, elements)
255
256
boundaries = init_boundaries(leaf_cell_ids, mesh, elements, dg.basis)
257
258
mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar)
259
260
mpi_mortars = init_mpi_mortars(leaf_cell_ids, mesh, elements, dg.mortar)
261
262
mpi_cache = init_mpi_cache(mesh, elements, mpi_interfaces, mpi_mortars,
263
nvariables(equations), nnodes(dg), uEltype)
264
265
# Container cache
266
cache = (; elements, interfaces, mpi_interfaces, boundaries, mortars,
267
mpi_mortars, mpi_cache)
268
269
# Add Volume-Integral cache
270
cache = (; cache...,
271
create_cache(mesh, equations, dg.volume_integral, dg, cache, uEltype)...)
272
# Add Mortar cache
273
cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...)
274
275
return cache
276
end
277
278
function init_mpi_cache(mesh, elements, mpi_interfaces, mpi_mortars, nvars, nnodes,
279
uEltype)
280
mpi_cache = MPICache(uEltype)
281
282
init_mpi_cache!(mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, nvars,
283
nnodes, uEltype)
284
return mpi_cache
285
end
286
287
function init_mpi_cache!(mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, nvars,
288
nnodes, uEltype)
289
mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars = init_mpi_neighbor_connectivity(elements,
290
mpi_interfaces,
291
mpi_mortars,
292
mesh)
293
294
mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests = init_mpi_data_structures(mpi_neighbor_interfaces,
295
mpi_neighbor_mortars,
296
ndims(mesh),
297
nvars,
298
nnodes,
299
uEltype)
300
301
# Determine local and total number of elements
302
n_elements_by_rank = Vector{Int}(undef, mpi_nranks())
303
n_elements_by_rank[mpi_rank() + 1] = nelements(elements)
304
MPI.Allgather!(MPI.UBuffer(n_elements_by_rank, 1), mpi_comm())
305
n_elements_by_rank = OffsetArray(n_elements_by_rank, 0:(mpi_nranks() - 1))
306
n_elements_global = MPI.Allreduce(nelements(elements), +, mpi_comm())
307
@assert n_elements_global==sum(n_elements_by_rank) "error in total number of elements"
308
309
# Determine the global element id of the first element
310
first_element_global_id = MPI.Exscan(nelements(elements), +, mpi_comm())
311
if mpi_isroot()
312
# With Exscan, the result on the first rank is undefined
313
first_element_global_id = 1
314
else
315
# On all other ranks we need to add one, since Julia has one-based indices
316
first_element_global_id += 1
317
end
318
# TODO reuse existing structures
319
@pack! mpi_cache = mpi_neighbor_ranks, mpi_neighbor_interfaces,
320
mpi_neighbor_mortars,
321
mpi_send_buffers, mpi_recv_buffers,
322
mpi_send_requests, mpi_recv_requests,
323
n_elements_by_rank, n_elements_global,
324
first_element_global_id
325
end
326
327
# Initialize connectivity between MPI neighbor ranks
328
function init_mpi_neighbor_connectivity(elements, mpi_interfaces, mpi_mortars,
329
mesh::TreeMesh2D)
330
tree = mesh.tree
331
332
# Determine neighbor ranks and sides for MPI interfaces
333
neighbor_ranks_interface = fill(-1, nmpiinterfaces(mpi_interfaces))
334
# The global interface id is the smaller of the (globally unique) neighbor cell ids, multiplied by
335
# number of directions (2 * ndims) plus direction minus one
336
global_interface_ids = fill(-1, nmpiinterfaces(mpi_interfaces))
337
for interface_id in 1:nmpiinterfaces(mpi_interfaces)
338
orientation = mpi_interfaces.orientations[interface_id]
339
remote_side = mpi_interfaces.remote_sides[interface_id]
340
# Direction is from local cell to remote cell
341
if orientation == 1 # MPI interface in x-direction
342
if remote_side == 1 # remote cell on the "left" of MPI interface
343
direction = 1
344
else # remote cell on the "right" of MPI interface
345
direction = 2
346
end
347
else # MPI interface in y-direction
348
if remote_side == 1 # remote cell on the "left" of MPI interface
349
direction = 3
350
else # remote cell on the "right" of MPI interface
351
direction = 4
352
end
353
end
354
local_neighbor_id = mpi_interfaces.local_neighbor_ids[interface_id]
355
local_cell_id = elements.cell_ids[local_neighbor_id]
356
remote_cell_id = tree.neighbor_ids[direction, local_cell_id]
357
neighbor_ranks_interface[interface_id] = tree.mpi_ranks[remote_cell_id]
358
if local_cell_id < remote_cell_id
359
global_interface_ids[interface_id] = 2 * ndims(tree) * local_cell_id +
360
direction - 1
361
else
362
global_interface_ids[interface_id] = (2 * ndims(tree) * remote_cell_id +
363
opposite_direction(direction) - 1)
364
end
365
end
366
367
# Determine neighbor ranks for MPI mortars
368
neighbor_ranks_mortar = Vector{Vector{Int}}(undef, nmpimortars(mpi_mortars))
369
# The global mortar id is the (globally unique) large cell id, multiplied by
370
# number of directions (2 * ndims) plus direction minus one where
371
# direction = 1 for mortars in x-direction where large element is left
372
# direction = 2 for mortars in x-direction where large element is right
373
# direction = 3 for mortars in y-direction where large element is left
374
# direction = 4 for mortars in y-direction where large element is right
375
global_mortar_ids = fill(-1, nmpimortars(mpi_mortars))
376
for mortar in 1:nmpimortars(mpi_mortars)
377
neighbor_ranks_mortar[mortar] = Vector{Int}()
378
379
orientation = mpi_mortars.orientations[mortar]
380
large_side = mpi_mortars.large_sides[mortar]
381
direction = (orientation - 1) * 2 + large_side
382
383
local_neighbor_ids = mpi_mortars.local_neighbor_ids[mortar]
384
local_neighbor_positions = mpi_mortars.local_neighbor_positions[mortar]
385
if 3 in local_neighbor_positions # large element is on this rank
386
large_element_id = local_neighbor_ids[findfirst(pos -> pos == 3,
387
local_neighbor_positions)]
388
large_cell_id = elements.cell_ids[large_element_id]
389
else # large element is remote
390
cell_id = elements.cell_ids[first(local_neighbor_ids)]
391
large_cell_id = tree.neighbor_ids[direction, tree.parent_ids[cell_id]]
392
end
393
394
neighbor_cell_id = tree.neighbor_ids[opposite_direction(direction),
395
large_cell_id]
396
if direction == 1
397
lower_cell_id = tree.child_ids[1, neighbor_cell_id]
398
upper_cell_id = tree.child_ids[3, neighbor_cell_id]
399
elseif direction == 2
400
lower_cell_id = tree.child_ids[2, neighbor_cell_id]
401
upper_cell_id = tree.child_ids[4, neighbor_cell_id]
402
elseif direction == 3
403
lower_cell_id = tree.child_ids[1, neighbor_cell_id]
404
upper_cell_id = tree.child_ids[2, neighbor_cell_id]
405
else
406
lower_cell_id = tree.child_ids[3, neighbor_cell_id]
407
upper_cell_id = tree.child_ids[4, neighbor_cell_id]
408
end
409
410
for cell_id in (lower_cell_id, upper_cell_id, large_cell_id)
411
if !is_own_cell(tree, cell_id)
412
neighbor_rank = tree.mpi_ranks[cell_id]
413
if !(neighbor_rank in neighbor_ranks_mortar[mortar])
414
push!(neighbor_ranks_mortar[mortar], neighbor_rank)
415
end
416
end
417
end
418
419
global_mortar_ids[mortar] = 2 * ndims(tree) * large_cell_id + direction - 1
420
end
421
422
# Get sorted, unique neighbor ranks
423
mpi_neighbor_ranks = vcat(neighbor_ranks_interface, neighbor_ranks_mortar...) |>
424
sort |> unique
425
426
# Sort interfaces by global interface id
427
p = sortperm(global_interface_ids)
428
neighbor_ranks_interface .= neighbor_ranks_interface[p]
429
interface_ids = collect(1:nmpiinterfaces(mpi_interfaces))[p]
430
431
# Sort mortars by global mortar id
432
p = sortperm(global_mortar_ids)
433
neighbor_ranks_mortar .= neighbor_ranks_mortar[p]
434
mortar_ids = collect(1:nmpimortars(mpi_mortars))[p]
435
436
# For each neighbor rank, init connectivity data structures
437
mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks))
438
mpi_neighbor_mortars = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks))
439
for (index, rank) in enumerate(mpi_neighbor_ranks)
440
mpi_neighbor_interfaces[index] = interface_ids[findall(x -> (x == rank),
441
neighbor_ranks_interface)]
442
mpi_neighbor_mortars[index] = mortar_ids[findall(x -> (rank in x),
443
neighbor_ranks_mortar)]
444
end
445
446
# Sanity checks that we counted all interfaces exactly once
447
@assert sum(length(v) for v in mpi_neighbor_interfaces) ==
448
nmpiinterfaces(mpi_interfaces)
449
450
return mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars
451
end
452
453
function rhs!(du, u, t,
454
mesh::Union{TreeMeshParallel{2}, P4estMeshParallel{2},
455
T8codeMeshParallel{2}}, equations,
456
boundary_conditions, source_terms::Source,
457
dg::DG, cache) where {Source}
458
backend = trixi_backend(u)
459
460
# Start to receive MPI data
461
@trixi_timeit timer() "start MPI receive" start_mpi_receive!(cache.mpi_cache)
462
463
# Prolong solution to MPI interfaces
464
@trixi_timeit timer() "prolong2mpiinterfaces" begin
465
prolong2mpiinterfaces!(cache, u, mesh, equations, dg.surface_integral, dg)
466
end
467
468
# Prolong solution to MPI mortars
469
@trixi_timeit timer() "prolong2mpimortars" begin
470
prolong2mpimortars!(cache, u, mesh, equations,
471
dg.mortar, dg)
472
end
473
474
# Start to send MPI data
475
@trixi_timeit timer() "start MPI send" begin
476
start_mpi_send!(cache.mpi_cache, mesh, equations, dg, cache)
477
end
478
479
# Reset du
480
@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)
481
482
# Calculate volume integral
483
@trixi_timeit timer() "volume integral" begin
484
calc_volume_integral!(backend, du, u, mesh,
485
have_nonconservative_terms(equations), equations,
486
dg.volume_integral, dg, cache)
487
end
488
489
# Prolong solution to interfaces
490
# TODO: Taal decide order of arguments, consistent vs. modified cache first?
491
@trixi_timeit timer() "prolong2interfaces" begin
492
prolong2interfaces!(backend, cache, u, mesh, equations, dg)
493
end
494
495
# Calculate interface fluxes
496
@trixi_timeit timer() "interface flux" begin
497
calc_interface_flux!(backend, cache.elements.surface_flux_values, mesh,
498
have_nonconservative_terms(equations), equations,
499
dg.surface_integral, dg, cache)
500
end
501
502
# Prolong solution to boundaries
503
@trixi_timeit timer() "prolong2boundaries" begin
504
prolong2boundaries!(cache, u, mesh, equations, dg)
505
end
506
507
# Calculate boundary fluxes
508
@trixi_timeit timer() "boundary flux" begin
509
calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations,
510
dg.surface_integral, dg)
511
end
512
513
# Prolong solution to mortars
514
@trixi_timeit timer() "prolong2mortars" begin
515
prolong2mortars!(cache, u, mesh, equations,
516
dg.mortar, dg)
517
end
518
519
# Calculate mortar fluxes
520
@trixi_timeit timer() "mortar flux" begin
521
calc_mortar_flux!(cache.elements.surface_flux_values, mesh,
522
have_nonconservative_terms(equations), equations,
523
dg.mortar, dg.surface_integral, dg, cache)
524
end
525
526
# Finish to receive MPI data
527
@trixi_timeit timer() "finish MPI receive" begin
528
finish_mpi_receive!(cache.mpi_cache, mesh, equations, dg, cache)
529
end
530
531
# Calculate MPI interface fluxes
532
@trixi_timeit timer() "MPI interface flux" begin
533
calc_mpi_interface_flux!(cache.elements.surface_flux_values, mesh,
534
have_nonconservative_terms(equations), equations,
535
dg.surface_integral, dg, cache)
536
end
537
538
# Calculate MPI mortar fluxes
539
@trixi_timeit timer() "MPI mortar flux" begin
540
calc_mpi_mortar_flux!(cache.elements.surface_flux_values, mesh,
541
have_nonconservative_terms(equations), equations,
542
dg.mortar, dg.surface_integral, dg, cache)
543
end
544
545
# Calculate surface integrals
546
@trixi_timeit timer() "surface integral" begin
547
calc_surface_integral!(backend, du, u, mesh, equations,
548
dg.surface_integral, dg, cache)
549
end
550
551
# Apply Jacobian from mapping to reference element
552
@trixi_timeit timer() "Jacobian" apply_jacobian!(backend, du, mesh, equations, dg,
553
cache)
554
555
# Calculate source terms
556
@trixi_timeit timer() "source terms" begin
557
calc_sources!(du, u, t, source_terms, equations, dg, cache)
558
end
559
560
# Finish to send MPI data
561
@trixi_timeit timer() "finish MPI send" finish_mpi_send!(cache.mpi_cache)
562
563
return nothing
564
end
565
566
function prolong2mpiinterfaces!(cache, u,
567
mesh::TreeMeshParallel{2},
568
equations, surface_integral, dg::DG)
569
@unpack mpi_interfaces = cache
570
571
@threaded for interface in eachmpiinterface(dg, cache)
572
local_element = mpi_interfaces.local_neighbor_ids[interface]
573
574
if mpi_interfaces.orientations[interface] == 1 # interface in x-direction
575
if mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction
576
for j in eachnode(dg), v in eachvariable(equations)
577
mpi_interfaces.u[2, v, j, interface] = u[v, 1, j, local_element]
578
end
579
else # local element in negative direction
580
for j in eachnode(dg), v in eachvariable(equations)
581
mpi_interfaces.u[1, v, j, interface] = u[v, nnodes(dg), j,
582
local_element]
583
end
584
end
585
else # interface in y-direction
586
if mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction
587
for i in eachnode(dg), v in eachvariable(equations)
588
mpi_interfaces.u[2, v, i, interface] = u[v, i, 1, local_element]
589
end
590
else # local element in negative direction
591
for i in eachnode(dg), v in eachvariable(equations)
592
mpi_interfaces.u[1, v, i, interface] = u[v, i, nnodes(dg),
593
local_element]
594
end
595
end
596
end
597
end
598
599
return nothing
600
end
601
602
function prolong2mpimortars!(cache, u,
603
mesh::TreeMeshParallel{2}, equations,
604
mortar_l2::LobattoLegendreMortarL2,
605
dg::DGSEM)
606
@unpack mpi_mortars = cache
607
608
@threaded for mortar in eachmpimortar(dg, cache)
609
local_neighbor_ids = mpi_mortars.local_neighbor_ids[mortar]
610
local_neighbor_positions = mpi_mortars.local_neighbor_positions[mortar]
611
612
for (element, position) in zip(local_neighbor_ids, local_neighbor_positions)
613
if position in (1, 2) # Current element is small
614
# Copy solution small to small
615
if mpi_mortars.large_sides[mortar] == 1 # -> small elements on right side
616
if mpi_mortars.orientations[mortar] == 1
617
# L2 mortars in x-direction
618
if position == 1
619
for l in eachnode(dg)
620
for v in eachvariable(equations)
621
mpi_mortars.u_lower[2, v, l, mortar] = u[v, 1, l,
622
element]
623
end
624
end
625
else # position == 2
626
for l in eachnode(dg)
627
for v in eachvariable(equations)
628
mpi_mortars.u_upper[2, v, l, mortar] = u[v, 1, l,
629
element]
630
end
631
end
632
end
633
else
634
# L2 mortars in y-direction
635
if position == 1
636
for l in eachnode(dg)
637
for v in eachvariable(equations)
638
mpi_mortars.u_lower[2, v, l, mortar] = u[v, l, 1,
639
element]
640
end
641
end
642
else # position == 2
643
for l in eachnode(dg)
644
for v in eachvariable(equations)
645
mpi_mortars.u_upper[2, v, l, mortar] = u[v, l, 1,
646
element]
647
end
648
end
649
end
650
end
651
else # large_sides[mortar] == 2 -> small elements on left side
652
if mpi_mortars.orientations[mortar] == 1
653
# L2 mortars in x-direction
654
if position == 1
655
for l in eachnode(dg)
656
for v in eachvariable(equations)
657
mpi_mortars.u_lower[1, v, l, mortar] = u[v,
658
nnodes(dg),
659
l, element]
660
end
661
end
662
else # position == 2
663
for l in eachnode(dg)
664
for v in eachvariable(equations)
665
mpi_mortars.u_upper[1, v, l, mortar] = u[v,
666
nnodes(dg),
667
l, element]
668
end
669
end
670
end
671
else
672
# L2 mortars in y-direction
673
if position == 1
674
for l in eachnode(dg)
675
for v in eachvariable(equations)
676
mpi_mortars.u_lower[1, v, l, mortar] = u[v, l,
677
nnodes(dg),
678
element]
679
end
680
end
681
else # position == 2
682
for l in eachnode(dg)
683
for v in eachvariable(equations)
684
mpi_mortars.u_upper[1, v, l, mortar] = u[v, l,
685
nnodes(dg),
686
element]
687
end
688
end
689
end
690
end
691
end
692
else # position == 3 -> current element is large
693
# Interpolate large element face data to small interface locations
694
if mpi_mortars.large_sides[mortar] == 1 # -> large element on left side
695
leftright = 1
696
if mpi_mortars.orientations[mortar] == 1
697
# L2 mortars in x-direction
698
u_large = view(u, :, nnodes(dg), :, element)
699
element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright,
700
mortar, u_large)
701
else
702
# L2 mortars in y-direction
703
u_large = view(u, :, :, nnodes(dg), element)
704
element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright,
705
mortar, u_large)
706
end
707
else # large_sides[mortar] == 2 -> large element on right side
708
leftright = 2
709
if mpi_mortars.orientations[mortar] == 1
710
# L2 mortars in x-direction
711
u_large = view(u, :, 1, :, element)
712
element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright,
713
mortar, u_large)
714
else
715
# L2 mortars in y-direction
716
u_large = view(u, :, :, 1, element)
717
element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright,
718
mortar, u_large)
719
end
720
end
721
end
722
end
723
end
724
725
return nothing
726
end
727
728
function calc_mpi_interface_flux!(surface_flux_values,
729
mesh::TreeMeshParallel{2},
730
have_nonconservative_terms::False, equations,
731
surface_integral, dg::DG, cache)
732
@unpack surface_flux = surface_integral
733
@unpack u, local_neighbor_ids, orientations, remote_sides = cache.mpi_interfaces
734
735
@threaded for interface in eachmpiinterface(dg, cache)
736
# Get local neighboring element
737
element = local_neighbor_ids[interface]
738
739
# Determine interface direction with respect to element:
740
if orientations[interface] == 1 # interface in x-direction
741
if remote_sides[interface] == 1 # local element in positive direction
742
direction = 1
743
else # local element in negative direction
744
direction = 2
745
end
746
else # interface in y-direction
747
if remote_sides[interface] == 1 # local element in positive direction
748
direction = 3
749
else # local element in negative direction
750
direction = 4
751
end
752
end
753
754
for i in eachnode(dg)
755
# Call pointwise Riemann solver
756
u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface)
757
flux = surface_flux(u_ll, u_rr, orientations[interface], equations)
758
759
# Copy flux to local element storage
760
for v in eachvariable(equations)
761
surface_flux_values[v, i, direction, element] = flux[v]
762
end
763
end
764
end
765
766
return nothing
767
end
768
769
function calc_mpi_mortar_flux!(surface_flux_values,
770
mesh::TreeMeshParallel{2},
771
have_nonconservative_terms::False, equations,
772
mortar_l2::LobattoLegendreMortarL2,
773
surface_integral, dg::DG, cache)
774
@unpack surface_flux = surface_integral
775
@unpack u_lower, u_upper, orientations = cache.mpi_mortars
776
@unpack fstar_primary_upper_threaded, fstar_primary_lower_threaded, fstar_secondary_upper_threaded, fstar_secondary_lower_threaded = cache
777
778
@threaded for mortar in eachmpimortar(dg, cache)
779
# Choose thread-specific pre-allocated container
780
fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()]
781
fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()]
782
fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()]
783
fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()]
784
785
# Because `have_nonconservative_terms` is `False` the primary and secondary fluxes
786
# are identical. So, we could possibly save on computation and just pass two copies later.
787
orientation = orientations[mortar]
788
calc_fstar!(fstar_primary_upper, equations, surface_flux, dg, u_upper, mortar,
789
orientation)
790
calc_fstar!(fstar_primary_lower, equations, surface_flux, dg, u_lower, mortar,
791
orientation)
792
calc_fstar!(fstar_secondary_upper, equations, surface_flux, dg, u_upper, mortar,
793
orientation)
794
calc_fstar!(fstar_secondary_lower, equations, surface_flux, dg, u_lower, mortar,
795
orientation)
796
797
mpi_mortar_fluxes_to_elements!(surface_flux_values,
798
mesh, equations, mortar_l2, dg, cache,
799
mortar, fstar_primary_upper, fstar_primary_lower,
800
fstar_secondary_upper, fstar_secondary_lower)
801
end
802
803
return nothing
804
end
805
806
@inline function mpi_mortar_fluxes_to_elements!(surface_flux_values,
807
mesh::TreeMeshParallel{2}, equations,
808
mortar_l2::LobattoLegendreMortarL2,
809
dg::DGSEM, cache,
810
mortar, fstar_primary_upper,
811
fstar_primary_lower,
812
fstar_secondary_upper,
813
fstar_secondary_lower)
814
local_neighbor_ids = cache.mpi_mortars.local_neighbor_ids[mortar]
815
local_neighbor_positions = cache.mpi_mortars.local_neighbor_positions[mortar]
816
817
for (element, position) in zip(local_neighbor_ids, local_neighbor_positions)
818
if position in (1, 2) # Current element is small
819
# Copy flux small to small
820
if cache.mpi_mortars.large_sides[mortar] == 1 # -> small elements on right side
821
if cache.mpi_mortars.orientations[mortar] == 1
822
# L2 mortars in x-direction
823
direction = 1
824
else
825
# L2 mortars in y-direction
826
direction = 3
827
end
828
else # large_sides[mortar] == 2 -> small elements on left side
829
if cache.mpi_mortars.orientations[mortar] == 1
830
# L2 mortars in x-direction
831
direction = 2
832
else
833
# L2 mortars in y-direction
834
direction = 4
835
end
836
end
837
838
if position == 1
839
surface_flux_values[:, :, direction, element] .= fstar_primary_lower
840
elseif position == 2
841
surface_flux_values[:, :, direction, element] .= fstar_primary_upper
842
end
843
else # position == 3 -> current element is large
844
# Project small fluxes to large element
845
if cache.mpi_mortars.large_sides[mortar] == 1 # -> large element on left side
846
if cache.mpi_mortars.orientations[mortar] == 1
847
# L2 mortars in x-direction
848
direction = 2
849
else
850
# L2 mortars in y-direction
851
direction = 4
852
end
853
else # large_sides[mortar] == 2 -> large element on right side
854
if cache.mpi_mortars.orientations[mortar] == 1
855
# L2 mortars in x-direction
856
direction = 1
857
else
858
# L2 mortars in y-direction
859
direction = 3
860
end
861
end
862
863
multiply_dimensionwise!(view(surface_flux_values, :, :, direction, element),
864
mortar_l2.reverse_upper, fstar_secondary_upper,
865
mortar_l2.reverse_lower, fstar_secondary_lower)
866
end
867
end
868
869
return nothing
870
end
871
end # @muladd
872
873