Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem_p4est/dg_3d_parabolic.jl
5616 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
# This version is called during `calc_gradients!` and must be specialized because the
9
# MAGNITUDE of the flux does NOT depend on the outward normal.
10
# Thus, you don't need to scale by 2 (e.g., the scaling factor in the normals (and in the
11
# contravariant vectors) along large/small elements across a non-conforming
12
# interface in 2D) and flip the sign when storing the mortar fluxes back
13
# into `surface_flux_values`.
14
@inline function mortar_fluxes_to_elements_gradient!(surface_flux_values,
15
mesh::P4estMesh{3},
16
equations_parabolic::AbstractEquationsParabolic,
17
mortar_l2::LobattoLegendreMortarL2,
18
dg::DGSEM, cache, mortar,
19
fstar_primary,
20
fstar_secondary, u_buffer,
21
fstar_tmp)
22
@unpack neighbor_ids, node_indices = cache.mortars
23
index_range = eachnode(dg)
24
# Copy solution small to small
25
small_indices = node_indices[1, mortar]
26
small_direction = indices2direction(small_indices)
27
28
for position in 1:4 # Loop over small elements
29
element = neighbor_ids[position, mortar]
30
for j in eachnode(dg), i in eachnode(dg)
31
for v in eachvariable(equations_parabolic)
32
surface_flux_values[v, i, j, small_direction, element] = fstar_primary[v,
33
i,
34
j,
35
position]
36
end
37
end
38
end
39
40
# Project small fluxes to large element.
41
multiply_dimensionwise!(u_buffer,
42
mortar_l2.reverse_lower, mortar_l2.reverse_lower,
43
view(fstar_secondary, .., 1), fstar_tmp)
44
add_multiply_dimensionwise!(u_buffer,
45
mortar_l2.reverse_upper, mortar_l2.reverse_lower,
46
view(fstar_secondary, .., 2), fstar_tmp)
47
add_multiply_dimensionwise!(u_buffer,
48
mortar_l2.reverse_lower, mortar_l2.reverse_upper,
49
view(fstar_secondary, .., 3), fstar_tmp)
50
add_multiply_dimensionwise!(u_buffer,
51
mortar_l2.reverse_upper, mortar_l2.reverse_upper,
52
view(fstar_secondary, .., 4), fstar_tmp)
53
54
# Copy interpolated flux values from buffer to large element face in the
55
# correct orientation.
56
# Note that the index of the small sides will always run forward but
57
# the index of the large side might need to run backwards for flipped sides.
58
large_element = neighbor_ids[5, mortar]
59
large_indices = node_indices[2, mortar]
60
large_direction = indices2direction(large_indices)
61
large_surface_indices = surface_indices(large_indices)
62
63
i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_surface_indices[1],
64
index_range)
65
j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_surface_indices[2],
66
index_range)
67
68
# Note that the indices of the small sides will always run forward but
69
# the large indices might need to run backwards for flipped sides.
70
i_large = i_large_start
71
j_large = j_large_start
72
for j in eachnode(dg)
73
for i in eachnode(dg)
74
for v in eachvariable(equations_parabolic)
75
surface_flux_values[v, i_large, j_large, large_direction, large_element] = u_buffer[v,
76
i,
77
j]
78
end
79
i_large += i_large_step_i
80
j_large += j_large_step_i
81
end
82
i_large += i_large_step_j
83
j_large += j_large_step_j
84
end
85
86
return nothing
87
end
88
89
function calc_interface_flux_gradient!(surface_flux_values,
90
mesh::P4estMesh{3},
91
equations_parabolic,
92
dg::DG, parabolic_scheme, cache)
93
@unpack neighbor_ids, node_indices = cache.interfaces
94
@unpack contravariant_vectors = cache.elements
95
index_range = eachnode(dg)
96
97
@threaded for interface in eachinterface(dg, cache)
98
# Get element and side information on the primary element
99
primary_element = neighbor_ids[1, interface]
100
primary_indices = node_indices[1, interface]
101
primary_direction = indices2direction(primary_indices)
102
103
i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1],
104
index_range)
105
j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2],
106
index_range)
107
k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3],
108
index_range)
109
110
i_primary = i_primary_start
111
j_primary = j_primary_start
112
k_primary = k_primary_start
113
114
# Get element and side information on the secondary element
115
secondary_element = neighbor_ids[2, interface]
116
secondary_indices = node_indices[2, interface]
117
secondary_direction = indices2direction(secondary_indices)
118
secondary_surface_indices = surface_indices(secondary_indices)
119
120
# Get the surface indexing on the secondary element.
121
# Note that the indices of the primary side will always run forward but
122
# the secondary indices might need to run backwards for flipped sides.
123
i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[1],
124
index_range)
125
j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[2],
126
index_range)
127
i_secondary = i_secondary_start
128
j_secondary = j_secondary_start
129
130
for j in eachnode(dg)
131
for i in eachnode(dg)
132
normal_direction = get_normal_direction(primary_direction,
133
contravariant_vectors,
134
i_primary, j_primary, k_primary,
135
primary_element)
136
137
calc_interface_flux_gradient!(surface_flux_values, mesh,
138
equations_parabolic,
139
dg, parabolic_scheme, cache,
140
interface, normal_direction, i, j,
141
primary_direction, primary_element,
142
i_secondary, j_secondary,
143
secondary_direction, secondary_element)
144
145
# Increment the primary element indices
146
i_primary += i_primary_step_i
147
j_primary += j_primary_step_i
148
k_primary += k_primary_step_i
149
# Increment the secondary element surface indices
150
i_secondary += i_secondary_step_i
151
j_secondary += j_secondary_step_i
152
end
153
# Increment the primary element indices
154
i_primary += i_primary_step_j
155
j_primary += j_primary_step_j
156
k_primary += k_primary_step_j
157
# Increment the secondary element surface indices
158
i_secondary += i_secondary_step_j
159
j_secondary += j_secondary_step_j
160
end
161
end
162
163
return nothing
164
end
165
166
# This version is used for parabolic gradient computations
167
@inline function calc_interface_flux_gradient!(surface_flux_values, mesh::P4estMesh{3},
168
equations_parabolic,
169
dg::DG, parabolic_scheme, cache,
170
interface_index, normal_direction,
171
primary_i_node_index,
172
primary_j_node_index,
173
primary_direction_index,
174
primary_element_index,
175
secondary_i_node_index,
176
secondary_j_node_index,
177
secondary_direction_index,
178
secondary_element_index)
179
@unpack u = cache.interfaces
180
181
u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg,
182
primary_i_node_index, primary_j_node_index,
183
interface_index)
184
185
flux_ = flux_parabolic(u_ll, u_rr, normal_direction, Gradient(),
186
equations_parabolic, parabolic_scheme)
187
188
for v in eachvariable(equations_parabolic)
189
surface_flux_values[v, primary_i_node_index, primary_j_node_index, primary_direction_index, primary_element_index] = flux_[v]
190
# No sign flip required for gradient calculation because for parabolic terms,
191
# the normals are not embedded in `flux_` for gradient computations.
192
surface_flux_values[v, secondary_i_node_index, secondary_j_node_index, secondary_direction_index, secondary_element_index] = flux_[v]
193
end
194
195
return nothing
196
end
197
198
# This is the version used when calculating the divergence of the parabolic fluxes.
199
# Identical to weak-form volume integral/kernel for the purely hyperbolic case,
200
# except that the fluxes are here already precomputed in `calc_parabolic_fluxes!`
201
function calc_volume_integral!(du, flux_parabolic, mesh::P4estMesh{3},
202
equations_parabolic::AbstractEquationsParabolic,
203
dg::DGSEM, cache)
204
(; derivative_hat) = dg.basis
205
(; contravariant_vectors) = cache.elements
206
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic
207
208
@threaded for element in eachelement(dg, cache)
209
# Calculate volume terms in one element
210
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
211
flux1 = get_node_vars(flux_parabolic_x, equations_parabolic, dg,
212
i, j, k, element)
213
flux2 = get_node_vars(flux_parabolic_y, equations_parabolic, dg,
214
i, j, k, element)
215
flux3 = get_node_vars(flux_parabolic_z, equations_parabolic, dg,
216
i, j, k, element)
217
218
# Compute the contravariant flux by taking the scalar product of the
219
# first contravariant vector Ja^1 and the flux vector
220
Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors,
221
i, j, k, element)
222
contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + Ja13 * flux3
223
for ii in eachnode(dg)
224
multiply_add_to_node_vars!(du, derivative_hat[ii, i],
225
contravariant_flux1,
226
equations_parabolic, dg, ii, j, k, element)
227
end
228
229
# Compute the contravariant flux by taking the scalar product of the
230
# second contravariant vector Ja^2 and the flux vector
231
Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors,
232
i, j, k, element)
233
contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + Ja23 * flux3
234
for jj in eachnode(dg)
235
multiply_add_to_node_vars!(du, derivative_hat[jj, j],
236
contravariant_flux2,
237
equations_parabolic, dg, i, jj, k, element)
238
end
239
240
# Compute the contravariant flux by taking the scalar product of the
241
# second contravariant vector Ja^2 and the flux vector
242
Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors,
243
i, j, k, element)
244
contravariant_flux3 = Ja31 * flux1 + Ja32 * flux2 + Ja33 * flux3
245
for kk in eachnode(dg)
246
multiply_add_to_node_vars!(du, derivative_hat[kk, k],
247
contravariant_flux3,
248
equations_parabolic, dg, i, j, kk, element)
249
end
250
end
251
end
252
253
return nothing
254
end
255
256
# This is the version used when calculating the divergence of the parabolic fluxes.
257
# Specialization `flux_parabolic::Tuple` needed to
258
# avoid amibiguity with the hyperbolic version of `prolong2interfaces!` in dg_3d.jl
259
# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.
260
function prolong2interfaces!(cache, flux_parabolic::Tuple,
261
mesh::P4estMesh{3},
262
equations_parabolic::AbstractEquationsParabolic,
263
dg::DG)
264
(; interfaces) = cache
265
(; contravariant_vectors) = cache.elements
266
index_range = eachnode(dg)
267
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic
268
269
@threaded for interface in eachinterface(dg, cache)
270
# Copy solution data from the primary element using "delayed indexing" with
271
# a start value and a step size to get the correct face and orientation.
272
# Note that in the current implementation, the interface will be
273
# "aligned at the primary element", i.e., the index of the primary side
274
# will always run forwards.
275
primary_element = interfaces.neighbor_ids[1, interface]
276
primary_indices = interfaces.node_indices[1, interface]
277
primary_direction = indices2direction(primary_indices)
278
279
i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1],
280
index_range)
281
j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2],
282
index_range)
283
k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3],
284
index_range)
285
286
i_primary = i_primary_start
287
j_primary = j_primary_start
288
k_primary = k_primary_start
289
290
for j in eachnode(dg)
291
for i in eachnode(dg)
292
# this is the outward normal direction on the primary element
293
normal_direction = get_normal_direction(primary_direction,
294
contravariant_vectors,
295
i_primary, j_primary,
296
k_primary,
297
primary_element)
298
299
for v in eachvariable(equations_parabolic)
300
# OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*!
301
flux_parabolic = SVector(flux_parabolic_x[v,
302
i_primary,
303
j_primary,
304
k_primary,
305
primary_element],
306
flux_parabolic_y[v,
307
i_primary,
308
j_primary,
309
k_primary,
310
primary_element],
311
flux_parabolic_z[v,
312
i_primary,
313
j_primary,
314
k_primary,
315
primary_element])
316
317
interfaces.u[1, v, i, j, interface] = dot(flux_parabolic,
318
normal_direction)
319
end
320
i_primary += i_primary_step_i
321
j_primary += j_primary_step_i
322
k_primary += k_primary_step_i
323
end
324
i_primary += i_primary_step_j
325
j_primary += j_primary_step_j
326
k_primary += k_primary_step_j
327
end
328
329
# Copy solution data from the secondary element using "delayed indexing" with
330
# a start value and a step size to get the correct face and orientation.
331
secondary_element = interfaces.neighbor_ids[2, interface]
332
secondary_indices = interfaces.node_indices[2, interface]
333
secondary_direction = indices2direction(secondary_indices)
334
335
i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_indices[1],
336
index_range)
337
j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_indices[2],
338
index_range)
339
k_secondary_start, k_secondary_step_i, k_secondary_step_j = index_to_start_step_3d(secondary_indices[3],
340
index_range)
341
342
i_secondary = i_secondary_start
343
j_secondary = j_secondary_start
344
k_secondary = k_secondary_start
345
for j in eachnode(dg)
346
for i in eachnode(dg)
347
# This is the outward normal direction on the secondary element.
348
# Here, we assume that normal_direction on the secondary element is
349
# the negative of normal_direction on the primary element.
350
normal_direction = get_normal_direction(secondary_direction,
351
contravariant_vectors,
352
i_secondary, j_secondary,
353
k_secondary,
354
secondary_element)
355
356
for v in eachvariable(equations_parabolic)
357
# OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*!
358
flux_parabolic = SVector(flux_parabolic_x[v,
359
i_secondary,
360
j_secondary,
361
k_secondary,
362
secondary_element],
363
flux_parabolic_y[v,
364
i_secondary,
365
j_secondary,
366
k_secondary,
367
secondary_element],
368
flux_parabolic_z[v,
369
i_secondary,
370
j_secondary,
371
k_secondary,
372
secondary_element])
373
# store the normal flux with respect to the primary normal direction,
374
# which is the negative of the secondary normal direction
375
interfaces.u[2, v, i, j, interface] = -dot(flux_parabolic,
376
normal_direction)
377
end
378
i_secondary += i_secondary_step_i
379
j_secondary += j_secondary_step_i
380
k_secondary += k_secondary_step_i
381
end
382
i_secondary += i_secondary_step_j
383
j_secondary += j_secondary_step_j
384
k_secondary += k_secondary_step_j
385
end
386
end
387
388
return nothing
389
end
390
391
# This version is used for divergence flux computations
392
function calc_interface_flux!(surface_flux_values, mesh::P4estMesh{3},
393
equations_parabolic, dg::DG, parabolic_scheme,
394
cache)
395
(; neighbor_ids, node_indices) = cache.interfaces
396
(; contravariant_vectors) = cache.elements
397
index_range = eachnode(dg)
398
399
@threaded for interface in eachinterface(dg, cache)
400
# Get element and side index information on the primary element
401
primary_element = neighbor_ids[1, interface]
402
primary_indices = node_indices[1, interface]
403
primary_direction_index = indices2direction(primary_indices)
404
405
i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1],
406
index_range)
407
j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2],
408
index_range)
409
k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3],
410
index_range)
411
412
i_primary = i_primary_start
413
j_primary = j_primary_start
414
k_primary = k_primary_start
415
416
# Get element and side index information on the secondary element
417
secondary_element = neighbor_ids[2, interface]
418
secondary_indices = node_indices[2, interface]
419
secondary_direction_index = indices2direction(secondary_indices)
420
secondary_surface_indices = surface_indices(secondary_indices)
421
422
# Initiate the secondary index to be used in the surface for loop.
423
# This index on the primary side will always run forward but
424
# the secondary index might need to run backwards for flipped sides.
425
# Get the surface indexing on the secondary element.
426
# Note that the indices of the primary side will always run forward but
427
# the secondary indices might need to run backwards for flipped sides.
428
i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[1],
429
index_range)
430
j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[2],
431
index_range)
432
i_secondary = i_secondary_start
433
j_secondary = j_secondary_start
434
435
for j in eachnode(dg)
436
for i in eachnode(dg)
437
normal_direction = get_normal_direction(primary_direction_index,
438
contravariant_vectors,
439
i_primary, j_primary, k_primary,
440
primary_element)
441
# We prolong the parabolic flux dotted with respect the outward normal on the
442
# primary element.
443
parabolic_flux_normal_ll, parabolic_flux_normal_rr = get_surface_node_vars(cache.interfaces.u,
444
equations_parabolic,
445
dg,
446
i,
447
j,
448
interface)
449
450
flux_ = flux_parabolic(parabolic_flux_normal_ll,
451
parabolic_flux_normal_rr,
452
normal_direction, Divergence(),
453
equations_parabolic, parabolic_scheme)
454
455
for v in eachvariable(equations_parabolic)
456
surface_flux_values[v, i, j, primary_direction_index, primary_element] = flux_[v]
457
# Sign flip required for divergence calculation since the flux for the
458
# divergence involves the normal direction.
459
surface_flux_values[v, i_secondary, j_secondary, secondary_direction_index, secondary_element] = -flux_[v]
460
end
461
462
# Increment the primary element indices
463
i_primary += i_primary_step_i
464
j_primary += j_primary_step_i
465
k_primary += k_primary_step_i
466
# Increment the secondary element surface indices
467
i_secondary += i_secondary_step_i
468
j_secondary += j_secondary_step_i
469
end
470
# Increment the primary element indices
471
i_primary += i_primary_step_j
472
j_primary += j_primary_step_j
473
k_primary += k_primary_step_j
474
# Increment the secondary element surface indices
475
i_secondary += i_secondary_step_j
476
j_secondary += j_secondary_step_j
477
end
478
end
479
480
return nothing
481
end
482
483
function prolong2mortars_divergence!(cache, flux_parabolic,
484
mesh::P4estMesh{3}, equations_parabolic,
485
mortar_l2::LobattoLegendreMortarL2,
486
dg::DGSEM)
487
@unpack neighbor_ids, node_indices = cache.mortars
488
@unpack fstar_tmp_threaded = cache
489
@unpack contravariant_vectors = cache.elements
490
index_range = eachnode(dg)
491
492
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic
493
494
@threaded for mortar in eachmortar(dg, cache)
495
# Copy solution data from the small elements using "delayed indexing" with
496
# a start value and a step size to get the correct face and orientation.
497
small_indices = node_indices[1, mortar]
498
direction_index = indices2direction(small_indices)
499
500
i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1],
501
index_range)
502
j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2],
503
index_range)
504
k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3],
505
index_range)
506
507
for position in 1:4 # Loop over small elements
508
i_small = i_small_start
509
j_small = j_small_start
510
k_small = k_small_start
511
element = neighbor_ids[position, mortar]
512
for j in eachnode(dg)
513
for i in eachnode(dg)
514
normal_direction = get_normal_direction(direction_index,
515
contravariant_vectors,
516
i_small, j_small, k_small,
517
element)
518
519
for v in eachvariable(equations_parabolic)
520
flux_parabolic = SVector(flux_parabolic_x[v, i_small, j_small,
521
k_small, element],
522
flux_parabolic_y[v, i_small, j_small,
523
k_small, element],
524
flux_parabolic_z[v, i_small, j_small,
525
k_small, element])
526
527
cache.mortars.u[1, v, position, i, j, mortar] = dot(flux_parabolic,
528
normal_direction)
529
end
530
i_small += i_small_step_i
531
j_small += j_small_step_i
532
k_small += k_small_step_i
533
end
534
i_small += i_small_step_j
535
j_small += j_small_step_j
536
k_small += k_small_step_j
537
end
538
end
539
540
# Buffer to copy solution values of the large element in the correct orientation
541
# before interpolating
542
u_buffer = cache.u_threaded[Threads.threadid()]
543
544
# temporary buffer for projections
545
fstar_tmp = fstar_tmp_threaded[Threads.threadid()]
546
547
# Copy solution of large element face to buffer in the
548
# correct orientation
549
large_indices = node_indices[2, mortar]
550
551
i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_indices[1],
552
index_range)
553
j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_indices[2],
554
index_range)
555
k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d(large_indices[3],
556
index_range)
557
558
i_large = i_large_start
559
j_large = j_large_start
560
k_large = k_large_start
561
element = neighbor_ids[5, mortar] # Large element
562
for j in eachnode(dg)
563
for i in eachnode(dg)
564
normal_direction = get_normal_direction(direction_index,
565
contravariant_vectors,
566
i_large, j_large, k_large,
567
element)
568
569
for v in eachvariable(equations_parabolic)
570
flux_parabolic = SVector(flux_parabolic_x[v,
571
i_large,
572
j_large,
573
k_large,
574
element],
575
flux_parabolic_y[v,
576
i_large,
577
j_large,
578
k_large,
579
element],
580
flux_parabolic_z[v,
581
i_large,
582
j_large,
583
k_large,
584
element])
585
586
# We prolong the parabolic flux dotted with respect the outward normal
587
# on the small element. We scale by -1/2 here because the normal
588
# direction on the large element is negative 2x that of the small
589
# element (these normal directions are "scaled" by the surface Jacobian)
590
u_buffer[v, i, j] = -0.5f0 * dot(flux_parabolic, normal_direction)
591
end
592
i_large += i_large_step_i
593
j_large += j_large_step_i
594
k_large += k_large_step_i
595
end
596
i_large += i_large_step_j
597
j_large += j_large_step_j
598
k_large += k_large_step_j
599
end
600
601
# Interpolate large element face data from buffer to small face locations
602
multiply_dimensionwise!(view(cache.mortars.u, 2, :, 1, :, :, mortar),
603
mortar_l2.forward_lower,
604
mortar_l2.forward_lower,
605
u_buffer, fstar_tmp)
606
multiply_dimensionwise!(view(cache.mortars.u, 2, :, 2, :, :, mortar),
607
mortar_l2.forward_upper,
608
mortar_l2.forward_lower,
609
u_buffer, fstar_tmp)
610
multiply_dimensionwise!(view(cache.mortars.u, 2, :, 3, :, :, mortar),
611
mortar_l2.forward_lower,
612
mortar_l2.forward_upper,
613
u_buffer, fstar_tmp)
614
multiply_dimensionwise!(view(cache.mortars.u, 2, :, 4, :, :, mortar),
615
mortar_l2.forward_upper,
616
mortar_l2.forward_upper,
617
u_buffer, fstar_tmp)
618
end
619
620
return nothing
621
end
622
623
function calc_mortar_flux_divergence!(surface_flux_values,
624
mesh::P4estMesh{3},
625
equations_parabolic,
626
mortar_l2::LobattoLegendreMortarL2,
627
dg::DG, parabolic_scheme, cache)
628
@unpack neighbor_ids, node_indices = cache.mortars
629
@unpack contravariant_vectors = cache.elements
630
@unpack fstar_primary_threaded, fstar_tmp_threaded = cache
631
index_range = eachnode(dg)
632
633
@threaded for mortar in eachmortar(dg, cache)
634
# Choose thread-specific pre-allocated container
635
fstar = fstar_primary_threaded[Threads.threadid()]
636
fstar_tmp = fstar_tmp_threaded[Threads.threadid()]
637
638
# Get index information on the small elements
639
small_indices = node_indices[1, mortar]
640
small_direction = indices2direction(small_indices)
641
642
i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1],
643
index_range)
644
j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2],
645
index_range)
646
k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3],
647
index_range)
648
649
for position in 1:4 # Loop over small elements
650
i_small = i_small_start
651
j_small = j_small_start
652
k_small = k_small_start
653
element = neighbor_ids[position, mortar]
654
for j in eachnode(dg)
655
for i in eachnode(dg)
656
normal_direction = get_normal_direction(small_direction,
657
contravariant_vectors,
658
i_small, j_small, k_small,
659
element)
660
661
for v in eachvariable(equations_parabolic)
662
parabolic_flux_normal_ll = cache.mortars.u[1, v, position, i, j,
663
mortar]
664
parabolic_flux_normal_rr = cache.mortars.u[2, v, position, i, j,
665
mortar]
666
667
flux_ = flux_parabolic(parabolic_flux_normal_ll,
668
parabolic_flux_normal_rr,
669
normal_direction, Divergence(),
670
equations_parabolic, parabolic_scheme)
671
672
# Sign flip (and scaling by 0.5) already handled above in `prolong2mortars_divergence!`
673
fstar[v, i, j, position] = flux_
674
end
675
676
i_small += i_small_step_i
677
j_small += j_small_step_i
678
k_small += k_small_step_i
679
end
680
i_small += i_small_step_j
681
j_small += j_small_step_j
682
k_small += k_small_step_j
683
end
684
end
685
686
# Buffer to interpolate flux values of the large element to before
687
# copying in the correct orientation
688
u_buffer = cache.u_threaded[Threads.threadid()]
689
690
# this reuses the hyperbolic version of `mortar_fluxes_to_elements!`
691
mortar_fluxes_to_elements!(surface_flux_values, mesh,
692
equations_parabolic, mortar_l2, dg, cache,
693
mortar, fstar, fstar, u_buffer,
694
fstar_tmp)
695
end
696
697
return nothing
698
end
699
700
function calc_mortar_flux_gradient!(surface_flux_values,
701
mesh::P4estMesh{3}, equations_parabolic,
702
mortar_l2::LobattoLegendreMortarL2,
703
dg::DG, parabolic_scheme, cache)
704
@unpack neighbor_ids, node_indices = cache.mortars
705
@unpack contravariant_vectors = cache.elements
706
@unpack fstar_primary_threaded, fstar_secondary_threaded, fstar_tmp_threaded = cache
707
index_range = eachnode(dg)
708
709
@threaded for mortar in eachmortar(dg, cache)
710
# Choose thread-specific pre-allocated container
711
fstar_primary = fstar_primary_threaded[Threads.threadid()]
712
fstar_secondary = fstar_secondary_threaded[Threads.threadid()]
713
fstar_tmp = fstar_tmp_threaded[Threads.threadid()]
714
715
# Get index information on the small elements
716
small_indices = node_indices[1, mortar]
717
small_direction = indices2direction(small_indices)
718
719
i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1],
720
index_range)
721
j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2],
722
index_range)
723
k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3],
724
index_range)
725
726
for position in 1:4
727
i_small = i_small_start
728
j_small = j_small_start
729
k_small = k_small_start
730
element = neighbor_ids[position, mortar]
731
for j in eachnode(dg)
732
for i in eachnode(dg)
733
normal_direction = get_normal_direction(small_direction,
734
contravariant_vectors,
735
i_small, j_small, k_small,
736
element)
737
738
calc_mortar_flux_gradient!(fstar_primary, fstar_secondary,
739
mesh, equations_parabolic,
740
dg, parabolic_scheme, cache,
741
mortar, position, normal_direction,
742
i, j)
743
744
i_small += i_small_step_i
745
j_small += j_small_step_i
746
k_small += k_small_step_i
747
end
748
i_small += i_small_step_j
749
j_small += j_small_step_j
750
k_small += k_small_step_j
751
end
752
end
753
754
# Buffer to interpolate flux values of the large element to before
755
# copying in the correct orientation
756
u_buffer = cache.u_threaded[Threads.threadid()]
757
758
# in calc_interface_flux!, the interface flux is computed once over each
759
# interface using the normal from the "primary" element. The result is then
760
# passed back to the "secondary" element, flipping the sign to account for the
761
# change in the normal direction. For mortars, this sign flip occurs in
762
# "mortar_fluxes_to_elements_gradient!" instead.
763
mortar_fluxes_to_elements_gradient!(surface_flux_values,
764
mesh, equations_parabolic, mortar_l2,
765
dg, cache,
766
mortar, fstar_primary, fstar_secondary,
767
u_buffer, fstar_tmp)
768
end
769
770
return nothing
771
end
772
773
# We structure `calc_mortar_flux_gradient!` similarly to "calc_mortar_flux!" for
774
# hyperbolic equations with no nonconservative terms.
775
# The reasoning is that parabolic fluxes are treated like conservative
776
# terms (e.g., we compute a parabolic conservative "flux") and thus no
777
# non-conservative terms are present.
778
@inline function calc_mortar_flux_gradient!(fstar_primary, fstar_secondary,
779
mesh::P4estMesh{3},
780
equations_parabolic,
781
dg::DG, parabolic_scheme, cache,
782
mortar_index, position_index,
783
normal_direction,
784
i_node_index, j_node_index)
785
@unpack u = cache.mortars
786
787
u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, position_index,
788
i_node_index, j_node_index, mortar_index)
789
790
flux_ = flux_parabolic(u_ll, u_rr, normal_direction, Gradient(),
791
equations_parabolic, parabolic_scheme)
792
793
# Copy flux to buffer
794
set_node_vars!(fstar_primary, flux_, equations_parabolic, dg,
795
i_node_index, j_node_index, position_index)
796
# As in `calc_interface_flux_gradient!`, no sign flip is necessary for the fluxes used in gradient calculations
797
set_node_vars!(fstar_secondary, flux_, equations_parabolic, dg,
798
i_node_index, j_node_index, position_index)
799
800
return nothing
801
end
802
803
function calc_volume_integral_gradient!(gradients, u_transformed,
804
mesh::P4estMesh{3}, # for dispatch only
805
equations_parabolic::AbstractEquationsParabolic,
806
dg::DG, cache)
807
@unpack derivative_hat = dg.basis
808
@unpack contravariant_vectors = cache.elements
809
gradients_x, gradients_y, gradients_z = gradients
810
811
@threaded for element in eachelement(dg, cache)
812
# Calculate volume terms in one element,
813
# corresponds to `kernel` functions for the hyperbolic part of the flux
814
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
815
u_node = get_node_vars(u_transformed, equations_parabolic, dg,
816
i, j, k, element)
817
818
for ii in eachnode(dg)
819
multiply_add_to_node_vars!(gradients_x, derivative_hat[ii, i],
820
u_node, equations_parabolic, dg,
821
ii, j, k, element)
822
end
823
824
for jj in eachnode(dg)
825
multiply_add_to_node_vars!(gradients_y, derivative_hat[jj, j],
826
u_node, equations_parabolic, dg,
827
i, jj, k, element)
828
end
829
830
for kk in eachnode(dg)
831
multiply_add_to_node_vars!(gradients_z, derivative_hat[kk, k],
832
u_node, equations_parabolic, dg,
833
i, j, kk, element)
834
end
835
end
836
837
# now that the reference coordinate gradients are computed, transform them node-by-node to physical gradients
838
# using the contravariant vectors
839
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
840
Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors,
841
i, j, k, element)
842
Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors,
843
i, j, k, element)
844
Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors,
845
i, j, k, element)
846
847
gradients_reference_1 = get_node_vars(gradients_x, equations_parabolic, dg,
848
i, j, k, element)
849
gradients_reference_2 = get_node_vars(gradients_y, equations_parabolic, dg,
850
i, j, k, element)
851
gradients_reference_3 = get_node_vars(gradients_z, equations_parabolic, dg,
852
i, j, k, element)
853
854
# note that the contravariant vectors are transposed compared with computations of flux
855
# divergences in `calc_volume_integral!`. See
856
# https://github.com/trixi-framework/Trixi.jl/pull/1490#discussion_r1213345190
857
# for a more detailed discussion.
858
gradient_x_node = Ja11 * gradients_reference_1 +
859
Ja21 * gradients_reference_2 +
860
Ja31 * gradients_reference_3
861
gradient_y_node = Ja12 * gradients_reference_1 +
862
Ja22 * gradients_reference_2 +
863
Ja32 * gradients_reference_3
864
gradient_z_node = Ja13 * gradients_reference_1 +
865
Ja23 * gradients_reference_2 +
866
Ja33 * gradients_reference_3
867
868
set_node_vars!(gradients_x, gradient_x_node, equations_parabolic, dg,
869
i, j, k, element)
870
set_node_vars!(gradients_y, gradient_y_node, equations_parabolic, dg,
871
i, j, k, element)
872
set_node_vars!(gradients_z, gradient_z_node, equations_parabolic, dg,
873
i, j, k, element)
874
end
875
end
876
877
return nothing
878
end
879
880
# Specialization `flux_parabolic::Tuple` needed to
881
# avoid amibiguity with the hyperbolic version of `prolong2boundaries!` in dg_3d.jl
882
# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.
883
function prolong2boundaries!(cache, flux_parabolic::Tuple,
884
mesh::P4estMesh{3},
885
equations_parabolic::AbstractEquationsParabolic,
886
dg::DG)
887
(; boundaries) = cache
888
(; contravariant_vectors) = cache.elements
889
index_range = eachnode(dg)
890
891
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic
892
893
@threaded for boundary in eachboundary(dg, cache)
894
# Copy solution data from the element using "delayed indexing" with
895
# a start value and a step size to get the correct face and orientation.
896
element = boundaries.neighbor_ids[boundary]
897
node_indices = boundaries.node_indices[boundary]
898
direction = indices2direction(node_indices)
899
900
i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1],
901
index_range)
902
j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2],
903
index_range)
904
k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3],
905
index_range)
906
907
i_node = i_node_start
908
j_node = j_node_start
909
k_node = k_node_start
910
911
for j in eachnode(dg)
912
for i in eachnode(dg)
913
# this is the outward normal direction on the primary element
914
normal_direction = get_normal_direction(direction,
915
contravariant_vectors,
916
i_node, j_node, k_node, element)
917
918
for v in eachvariable(equations_parabolic)
919
flux_parabolic = SVector(flux_parabolic_x[v, i_node, j_node, k_node,
920
element],
921
flux_parabolic_y[v, i_node, j_node, k_node,
922
element],
923
flux_parabolic_z[v, i_node, j_node, k_node,
924
element])
925
926
boundaries.u[v, i, j, boundary] = dot(flux_parabolic,
927
normal_direction)
928
end
929
i_node += i_node_step_i
930
j_node += j_node_step_i
931
k_node += k_node_step_i
932
end
933
i_node += i_node_step_j
934
j_node += j_node_step_j
935
k_node += k_node_step_j
936
end
937
end
938
return nothing
939
end
940
941
function calc_boundary_flux!(cache, t,
942
boundary_condition_parabolic, # works with Dict types
943
boundary_condition_indices,
944
operator_type, mesh::P4estMesh{3},
945
equations_parabolic::AbstractEquationsParabolic,
946
surface_integral, dg::DG)
947
(; boundaries) = cache
948
(; node_coordinates, surface_flux_values) = cache.elements
949
(; contravariant_vectors) = cache.elements
950
index_range = eachnode(dg)
951
952
@threaded for local_index in eachindex(boundary_condition_indices)
953
# Use the local index to get the global boundary index from the pre-sorted list
954
boundary_index = boundary_condition_indices[local_index]
955
956
# Get information on the adjacent element, compute the surface fluxes,
957
# and store them
958
element = boundaries.neighbor_ids[boundary_index]
959
node_indices = boundaries.node_indices[boundary_index]
960
direction_index = indices2direction(node_indices)
961
962
i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1],
963
index_range)
964
j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2],
965
index_range)
966
k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3],
967
index_range)
968
969
i_node = i_node_start
970
j_node = j_node_start
971
k_node = k_node_start
972
973
for j in eachnode(dg)
974
for i in eachnode(dg)
975
# Extract solution data from boundary container
976
u_inner = get_node_vars(boundaries.u, equations_parabolic, dg, i, j,
977
boundary_index)
978
979
# Outward-pointing normal direction (not normalized)
980
normal_direction = get_normal_direction(direction_index,
981
contravariant_vectors,
982
i_node, j_node, k_node, element)
983
984
# TODO: revisit if we want more general boundary treatments.
985
# This assumes the gradient numerical flux at the boundary is the gradient variable,
986
# which is consistent with BR1, LDG.
987
flux_inner = u_inner
988
989
# Coordinates at boundary node
990
x = get_node_coords(node_coordinates, equations_parabolic, dg,
991
i_node, j_node, k_node, element)
992
993
flux_ = boundary_condition_parabolic(flux_inner, u_inner,
994
normal_direction,
995
x, t, operator_type,
996
equations_parabolic)
997
998
# Copy flux to element storage in the correct orientation
999
for v in eachvariable(equations_parabolic)
1000
surface_flux_values[v, i, j, direction_index, element] = flux_[v]
1001
end
1002
1003
i_node += i_node_step_i
1004
j_node += j_node_step_i
1005
k_node += k_node_step_i
1006
end
1007
i_node += i_node_step_j
1008
j_node += j_node_step_j
1009
k_node += k_node_step_j
1010
end
1011
end
1012
1013
return nothing
1014
end
1015
1016
function calc_surface_integral_gradient!(gradients,
1017
mesh::P4estMesh{3}, # for dispatch only
1018
equations_parabolic::AbstractEquationsParabolic,
1019
dg::DGSEM, cache)
1020
@unpack inverse_weights = dg.basis
1021
@unpack surface_flux_values = cache.elements
1022
@unpack contravariant_vectors = cache.elements
1023
1024
# We also use explicit assignments instead of `+=` to let `@muladd` turn these
1025
# into FMAs (see comment at the top of the file).
1026
factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±1
1027
@threaded for element in eachelement(dg, cache)
1028
for l in eachnode(dg), m in eachnode(dg)
1029
for v in eachvariable(equations_parabolic)
1030
for dim in 1:3
1031
grad = gradients[dim]
1032
# surface at -x
1033
normal_direction = get_normal_direction(1, contravariant_vectors,
1034
1, l, m,
1035
element)
1036
grad[v, 1, l, m, element] = (grad[v,
1037
1, l, m,
1038
element] +
1039
surface_flux_values[v,
1040
l, m, 1,
1041
element] *
1042
factor *
1043
normal_direction[dim])
1044
1045
# surface at +x
1046
normal_direction = get_normal_direction(2, contravariant_vectors,
1047
nnodes(dg), l, m,
1048
element)
1049
grad[v, nnodes(dg), l, m, element] = (grad[v,
1050
nnodes(dg), l, m,
1051
element] +
1052
surface_flux_values[v,
1053
l, m, 2,
1054
element] *
1055
factor *
1056
normal_direction[dim])
1057
1058
# surface at -y
1059
normal_direction = get_normal_direction(3, contravariant_vectors,
1060
l, 1, m,
1061
element)
1062
grad[v, l, 1, m, element] = (grad[v,
1063
l, 1, m,
1064
element] +
1065
surface_flux_values[v,
1066
l, m, 3,
1067
element] *
1068
factor *
1069
normal_direction[dim])
1070
1071
# surface at +y
1072
normal_direction = get_normal_direction(4, contravariant_vectors,
1073
l, nnodes(dg), m,
1074
element)
1075
grad[v, l, nnodes(dg), m, element] = (grad[v,
1076
l, nnodes(dg), m,
1077
element] +
1078
surface_flux_values[v,
1079
l, m, 4,
1080
element] *
1081
factor *
1082
normal_direction[dim])
1083
1084
# surface at -z
1085
normal_direction = get_normal_direction(5, contravariant_vectors,
1086
l, m, 1,
1087
element)
1088
grad[v, l, m, 1, element] = (grad[v,
1089
l, m, 1,
1090
element] +
1091
surface_flux_values[v,
1092
l, m, 5,
1093
element] *
1094
factor *
1095
normal_direction[dim])
1096
1097
# surface at +z
1098
normal_direction = get_normal_direction(6, contravariant_vectors,
1099
l, m, nnodes(dg),
1100
element)
1101
grad[v, l, m, nnodes(dg), element] = (grad[v,
1102
l, m, nnodes(dg),
1103
element] +
1104
surface_flux_values[v,
1105
l, m, 6,
1106
element] *
1107
factor *
1108
normal_direction[dim])
1109
end
1110
end
1111
end
1112
end
1113
1114
return nothing
1115
end
1116
1117
# Needed to *not* flip the sign of the inverse Jacobian.
1118
# This is because the parabolic fluxes are assumed to be of the form
1119
# `du/dt + df/dx = dg/dx + source(x,t)`,
1120
# where f(u) is the inviscid flux and g(u) is the parabolic flux.
1121
function apply_jacobian_parabolic!(du::AbstractArray, mesh::P4estMesh{3},
1122
equations_parabolic::AbstractEquationsParabolic,
1123
dg::DG, cache)
1124
@unpack inverse_jacobian = cache.elements
1125
1126
@threaded for element in eachelement(dg, cache)
1127
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
1128
factor = inverse_jacobian[i, j, k, element]
1129
1130
for v in eachvariable(equations_parabolic)
1131
du[v, i, j, k, element] *= factor
1132
end
1133
end
1134
end
1135
1136
return nothing
1137
end
1138
end # @muladd
1139
1140