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_3d_parabolic.jl
5591 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 method is called when a `SemidiscretizationHyperbolicParabolic` is constructed.
9
# It constructs the basic `cache` used throughout the simulation to compute
10
# the RHS etc.
11
function create_cache_parabolic(mesh::Union{TreeMesh{3}, P4estMesh{3}},
12
equations_hyperbolic::AbstractEquations,
13
dg::DG, n_elements, uEltype)
14
parabolic_container = init_parabolic_container_3d(nvariables(equations_hyperbolic),
15
nnodes(dg), n_elements,
16
uEltype)
17
18
cache_parabolic = (; parabolic_container)
19
20
return cache_parabolic
21
end
22
23
# Transform solution variables prior to taking the gradient
24
# (e.g., conservative to primitive variables). Defaults to doing nothing.
25
# TODO: can we avoid copying data?
26
function transform_variables!(u_transformed, u, mesh::Union{TreeMesh{3}, P4estMesh{3}},
27
equations_parabolic::AbstractEquationsParabolic,
28
dg::DG, cache)
29
transformation = gradient_variable_transformation(equations_parabolic)
30
31
@threaded for element in eachelement(dg, cache)
32
# Calculate volume terms in one element
33
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
34
u_node = get_node_vars(u, equations_parabolic, dg, i, j, k, element)
35
u_transformed_node = transformation(u_node, equations_parabolic)
36
set_node_vars!(u_transformed, u_transformed_node, equations_parabolic, dg,
37
i, j, k, element)
38
end
39
end
40
41
return nothing
42
end
43
44
function reset_gradients!(gradients::NTuple{3}, dg::DG, cache)
45
gradients_x, gradients_y, gradients_z = gradients
46
47
set_zero!(gradients_x, dg, cache)
48
set_zero!(gradients_y, dg, cache)
49
set_zero!(gradients_z, dg, cache)
50
51
return nothing
52
end
53
54
# This is the version used when calculating the divergence of the parabolic fluxes.
55
# Identical to weak-form volume integral/kernel for the purely hyperbolic case,
56
# except that the fluxes are here already precomputed in `calc_parabolic_fluxes!`
57
function calc_volume_integral!(du, flux_parabolic, mesh::TreeMesh{3},
58
equations_parabolic::AbstractEquationsParabolic,
59
dg::DGSEM, cache)
60
@unpack derivative_hat = dg.basis
61
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic
62
63
@threaded for element in eachelement(dg, cache)
64
# Calculate volume terms in one element
65
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
66
flux_1_node = get_node_vars(flux_parabolic_x, equations_parabolic, dg,
67
i, j, k, element)
68
flux_2_node = get_node_vars(flux_parabolic_y, equations_parabolic, dg,
69
i, j, k, element)
70
flux_3_node = get_node_vars(flux_parabolic_z, equations_parabolic, dg,
71
i, j, k, element)
72
73
for ii in eachnode(dg)
74
multiply_add_to_node_vars!(du, derivative_hat[ii, i], flux_1_node,
75
equations_parabolic, dg, ii, j, k, element)
76
end
77
78
for jj in eachnode(dg)
79
multiply_add_to_node_vars!(du, derivative_hat[jj, j], flux_2_node,
80
equations_parabolic, dg, i, jj, k, element)
81
end
82
83
for kk in eachnode(dg)
84
multiply_add_to_node_vars!(du, derivative_hat[kk, k], flux_3_node,
85
equations_parabolic, dg, i, j, kk, element)
86
end
87
end
88
end
89
90
return nothing
91
end
92
93
# This is the version used when calculating the divergence of the parabolic fluxes.
94
# Specialization `flux_parabolic::Tuple` needed to
95
# avoid amibiguity with the hyperbolic version of `prolong2interfaces!` in dg_3d.jl
96
# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.
97
function prolong2interfaces!(cache, flux_parabolic::Tuple,
98
mesh::TreeMesh{3},
99
equations_parabolic::AbstractEquationsParabolic,
100
dg::DG)
101
@unpack interfaces = cache
102
@unpack orientations, neighbor_ids = interfaces
103
104
# OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*!
105
interfaces_u = interfaces.u
106
107
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic
108
109
@threaded for interface in eachinterface(dg, cache)
110
left_element = neighbor_ids[1, interface]
111
right_element = neighbor_ids[2, interface]
112
113
if orientations[interface] == 1
114
# interface in x-direction
115
for k in eachnode(dg), j in eachnode(dg),
116
v in eachvariable(equations_parabolic)
117
118
interfaces_u[1, v, j, k, interface] = flux_parabolic_x[v,
119
nnodes(dg), j, k,
120
left_element]
121
interfaces_u[2, v, j, k, interface] = flux_parabolic_x[v,
122
1, j, k,
123
right_element]
124
end
125
elseif orientations[interface] == 2
126
# interface in y-direction
127
for k in eachnode(dg), i in eachnode(dg),
128
v in eachvariable(equations_parabolic)
129
130
interfaces_u[1, v, i, k, interface] = flux_parabolic_y[v,
131
i, nnodes(dg), k,
132
left_element]
133
interfaces_u[2, v, i, k, interface] = flux_parabolic_y[v,
134
i, 1, k,
135
right_element]
136
end
137
else # if orientations[interface] == 3
138
# interface in z-direction
139
for j in eachnode(dg), i in eachnode(dg),
140
v in eachvariable(equations_parabolic)
141
142
interfaces_u[1, v, i, j, interface] = flux_parabolic_z[v,
143
i, j, nnodes(dg),
144
left_element]
145
interfaces_u[2, v, i, j, interface] = flux_parabolic_z[v,
146
i, j, 1,
147
right_element]
148
end
149
end
150
end
151
152
return nothing
153
end
154
155
# This is the version used when calculating the divergence of the parabolic fluxes
156
function calc_interface_flux!(surface_flux_values, mesh::TreeMesh{3},
157
equations_parabolic, dg::DG, parabolic_scheme,
158
cache)
159
@unpack neighbor_ids, orientations = cache.interfaces
160
161
@threaded for interface in eachinterface(dg, cache)
162
# Get neighboring elements
163
left_id = neighbor_ids[1, interface]
164
right_id = neighbor_ids[2, interface]
165
166
# Determine interface direction with respect to elements:
167
# orientation = 1: left -> 2, right -> 1
168
# orientation = 2: left -> 4, right -> 3
169
# orientation = 3: left -> 6, right -> 5
170
left_direction = 2 * orientations[interface]
171
right_direction = 2 * orientations[interface] - 1
172
173
for j in eachnode(dg), i in eachnode(dg)
174
# Get precomputed fluxes at interfaces
175
flux_ll, flux_rr = get_surface_node_vars(cache.interfaces.u,
176
equations_parabolic, dg,
177
i, j, interface)
178
179
flux = flux_parabolic(flux_ll, flux_rr, Divergence(),
180
equations_parabolic, parabolic_scheme)
181
182
# Copy flux to left and right element storage
183
for v in eachvariable(equations_parabolic)
184
surface_flux_values[v, i, j, left_direction, left_id] = flux[v]
185
surface_flux_values[v, i, j, right_direction, right_id] = flux[v]
186
end
187
end
188
end
189
190
return nothing
191
end
192
193
# This is the version used when calculating the divergence of the parabolic fluxes.
194
# Specialization `flux_parabolic::Tuple` needed to
195
# avoid amibiguity with the hyperbolic version of `prolong2boundaries!` in dg_3d.jl
196
# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.
197
function prolong2boundaries!(cache, flux_parabolic::Tuple,
198
mesh::TreeMesh{3},
199
equations_parabolic::AbstractEquationsParabolic,
200
dg::DG)
201
@unpack boundaries = cache
202
@unpack orientations, neighbor_sides, neighbor_ids = boundaries
203
204
# OBS! `boundaries_u` stores the "interpolated" *fluxes* and *not the solution*!
205
boundaries_u = boundaries.u
206
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic
207
208
@threaded for boundary in eachboundary(dg, cache)
209
element = neighbor_ids[boundary]
210
211
if orientations[boundary] == 1
212
# boundary in x-direction
213
if neighbor_sides[boundary] == 1
214
# element in -x direction of boundary
215
for k in eachnode(dg), j in eachnode(dg),
216
v in eachvariable(equations_parabolic)
217
218
boundaries_u[1, v, j, k, boundary] = flux_parabolic_x[v,
219
nnodes(dg),
220
j,
221
k,
222
element]
223
end
224
else # Element in +x direction of boundary
225
for k in eachnode(dg), j in eachnode(dg),
226
v in eachvariable(equations_parabolic)
227
228
boundaries_u[2, v, j, k, boundary] = flux_parabolic_x[v,
229
1,
230
j,
231
k,
232
element]
233
end
234
end
235
elseif orientations[boundary] == 2
236
# boundary in y-direction
237
if neighbor_sides[boundary] == 1
238
# element in -y direction of boundary
239
for k in eachnode(dg), i in eachnode(dg),
240
v in eachvariable(equations_parabolic)
241
242
boundaries_u[1, v, i, k, boundary] = flux_parabolic_y[v,
243
i,
244
nnodes(dg),
245
k,
246
element]
247
end
248
else
249
# element in +y direction of boundary
250
for k in eachnode(dg), i in eachnode(dg),
251
v in eachvariable(equations_parabolic)
252
253
boundaries_u[2, v, i, k, boundary] = flux_parabolic_y[v,
254
i,
255
1,
256
k,
257
element]
258
end
259
end
260
else # if orientations[boundary] == 3
261
# boundary in z-direction
262
if neighbor_sides[boundary] == 1
263
# element in -z direction of boundary
264
for j in eachnode(dg), i in eachnode(dg),
265
v in eachvariable(equations_parabolic)
266
267
boundaries_u[1, v, i, j, boundary] = flux_parabolic_z[v,
268
i,
269
j,
270
nnodes(dg),
271
element]
272
end
273
else
274
# element in +z direction of boundary
275
for j in eachnode(dg), i in eachnode(dg),
276
v in eachvariable(equations_parabolic)
277
278
boundaries_u[2, v, i, j, boundary] = flux_parabolic_z[v,
279
i,
280
j,
281
1,
282
element]
283
end
284
end
285
end
286
end
287
288
return nothing
289
end
290
291
function calc_parabolic_fluxes!(flux_parabolic,
292
gradients, u_transformed,
293
mesh::Union{TreeMesh{3}, P4estMesh{3}},
294
equations_parabolic::AbstractEquationsParabolic,
295
dg::DG, cache)
296
gradients_x, gradients_y, gradients_z = gradients
297
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic # output arrays
298
299
@threaded for element in eachelement(dg, cache)
300
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
301
# Get solution and gradients
302
u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, k,
303
element)
304
gradients_1_node = get_node_vars(gradients_x, equations_parabolic, dg,
305
i, j, k, element)
306
gradients_2_node = get_node_vars(gradients_y, equations_parabolic, dg,
307
i, j, k, element)
308
gradients_3_node = get_node_vars(gradients_z, equations_parabolic, dg,
309
i, j, k, element)
310
311
# Calculate parabolic flux and store each component for later use
312
flux_parabolic_node_x = flux(u_node,
313
(gradients_1_node, gradients_2_node,
314
gradients_3_node), 1, equations_parabolic)
315
flux_parabolic_node_y = flux(u_node,
316
(gradients_1_node, gradients_2_node,
317
gradients_3_node), 2, equations_parabolic)
318
flux_parabolic_node_z = flux(u_node,
319
(gradients_1_node, gradients_2_node,
320
gradients_3_node), 3, equations_parabolic)
321
set_node_vars!(flux_parabolic_x, flux_parabolic_node_x,
322
equations_parabolic, dg,
323
i, j, k, element)
324
set_node_vars!(flux_parabolic_y, flux_parabolic_node_y,
325
equations_parabolic, dg,
326
i, j, k, element)
327
set_node_vars!(flux_parabolic_z, flux_parabolic_node_z,
328
equations_parabolic, dg,
329
i, j, k, element)
330
end
331
end
332
333
return nothing
334
end
335
336
# TODO: parabolic; decide if we should keep this.
337
function get_unsigned_normal_vector_3d(direction)
338
if direction > 6 || direction < 1
339
error("Direction = $direction; in 3D, direction should be 1, 2, 3, 4, 5, or 6.")
340
end
341
if direction == 1 || direction == 2
342
return SVector(1.0, 0.0, 0.0)
343
elseif direction == 3 || direction == 4
344
return SVector(0.0, 1.0, 0.0)
345
else
346
return SVector(0.0, 0.0, 1.0)
347
end
348
end
349
350
function calc_boundary_flux_gradient!(cache, t,
351
boundary_conditions_parabolic::BoundaryConditionPeriodic,
352
mesh::Union{TreeMesh{3}, P4estMesh{3}},
353
equations_parabolic::AbstractEquationsParabolic,
354
surface_integral, dg::DG)
355
return nothing
356
end
357
358
function calc_boundary_flux_divergence!(cache, t,
359
boundary_conditions_parabolic::BoundaryConditionPeriodic,
360
mesh::Union{TreeMesh{3}, P4estMesh{3}},
361
equations_parabolic::AbstractEquationsParabolic,
362
surface_integral, dg::DG)
363
return nothing
364
end
365
366
function calc_boundary_flux_gradient!(cache, t,
367
boundary_conditions_parabolic::NamedTuple,
368
mesh::TreeMesh{3}, # for dispatch only
369
equations_parabolic::AbstractEquationsParabolic,
370
surface_integral, dg::DG)
371
@unpack surface_flux_values = cache.elements
372
@unpack n_boundaries_per_direction = cache.boundaries
373
374
# Calculate indices
375
lasts = accumulate(+, n_boundaries_per_direction)
376
firsts = lasts - n_boundaries_per_direction .+ 1
377
378
# Calc boundary fluxes in each direction
379
calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,
380
boundary_conditions_parabolic[1],
381
equations_parabolic, surface_integral,
382
dg, cache,
383
1, firsts[1], lasts[1])
384
calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,
385
boundary_conditions_parabolic[2],
386
equations_parabolic, surface_integral,
387
dg, cache,
388
2, firsts[2], lasts[2])
389
calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,
390
boundary_conditions_parabolic[3],
391
equations_parabolic, surface_integral,
392
dg, cache,
393
3, firsts[3], lasts[3])
394
calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,
395
boundary_conditions_parabolic[4],
396
equations_parabolic, surface_integral,
397
dg, cache,
398
4, firsts[4], lasts[4])
399
calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,
400
boundary_conditions_parabolic[5],
401
equations_parabolic, surface_integral,
402
dg, cache,
403
5, firsts[5], lasts[5])
404
calc_boundary_flux_by_direction_gradient!(surface_flux_values, t,
405
boundary_conditions_parabolic[6],
406
equations_parabolic, surface_integral,
407
dg, cache,
408
6, firsts[6], lasts[6])
409
410
return nothing
411
end
412
413
function calc_boundary_flux_by_direction_gradient!(surface_flux_values::AbstractArray{<:Any,
414
5},
415
t, boundary_condition,
416
equations_parabolic::AbstractEquationsParabolic,
417
surface_integral, dg::DG, cache,
418
direction, first_boundary,
419
last_boundary)
420
@unpack surface_flux = surface_integral
421
@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries
422
423
@threaded for boundary in first_boundary:last_boundary
424
# Get neighboring element
425
neighbor = neighbor_ids[boundary]
426
427
for j in eachnode(dg), i in eachnode(dg)
428
# Get boundary flux
429
u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg,
430
i, j, boundary)
431
if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right
432
u_inner = u_ll
433
else # Element is on the right, boundary on the left
434
u_inner = u_rr
435
end
436
437
# TODO: revisit if we want more general boundary treatments.
438
# This assumes the gradient numerical flux at the boundary is the gradient variable,
439
# which is consistent with BR1, LDG.
440
flux_inner = u_inner
441
442
x = get_node_coords(node_coordinates, equations_parabolic, dg,
443
i, j, boundary)
444
flux = boundary_condition(flux_inner, u_inner,
445
get_unsigned_normal_vector_3d(direction),
446
x, t, Gradient(), equations_parabolic)
447
448
# Copy flux to left and right element storage
449
for v in eachvariable(equations_parabolic)
450
surface_flux_values[v, i, j, direction, neighbor] = flux[v]
451
end
452
end
453
end
454
455
return nothing
456
end
457
458
function calc_boundary_flux_divergence!(cache, t,
459
boundary_conditions_parabolic::NamedTuple,
460
mesh::TreeMesh{3},
461
equations_parabolic::AbstractEquationsParabolic,
462
surface_integral, dg::DG)
463
@unpack surface_flux_values = cache.elements
464
@unpack n_boundaries_per_direction = cache.boundaries
465
466
# Calculate indices
467
lasts = accumulate(+, n_boundaries_per_direction)
468
firsts = lasts - n_boundaries_per_direction .+ 1
469
470
# Calc boundary fluxes in each direction
471
calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,
472
boundary_conditions_parabolic[1],
473
equations_parabolic, surface_integral,
474
dg, cache,
475
1, firsts[1], lasts[1])
476
calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,
477
boundary_conditions_parabolic[2],
478
equations_parabolic, surface_integral,
479
dg, cache,
480
2, firsts[2], lasts[2])
481
calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,
482
boundary_conditions_parabolic[3],
483
equations_parabolic, surface_integral,
484
dg, cache,
485
3, firsts[3], lasts[3])
486
calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,
487
boundary_conditions_parabolic[4],
488
equations_parabolic, surface_integral,
489
dg, cache,
490
4, firsts[4], lasts[4])
491
calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,
492
boundary_conditions_parabolic[5],
493
equations_parabolic, surface_integral,
494
dg, cache,
495
5, firsts[5], lasts[5])
496
calc_boundary_flux_by_direction_divergence!(surface_flux_values, t,
497
boundary_conditions_parabolic[6],
498
equations_parabolic, surface_integral,
499
dg, cache,
500
6, firsts[6], lasts[6])
501
502
return nothing
503
end
504
505
function calc_boundary_flux_by_direction_divergence!(surface_flux_values::AbstractArray{<:Any,
506
5},
507
t,
508
boundary_condition,
509
equations_parabolic::AbstractEquationsParabolic,
510
surface_integral, dg::DG, cache,
511
direction, first_boundary,
512
last_boundary)
513
@unpack surface_flux = surface_integral
514
515
# Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction")
516
# of the parabolic flux, as computed in `prolong2boundaries!`
517
@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries
518
519
@threaded for boundary in first_boundary:last_boundary
520
# Get neighboring element
521
neighbor = neighbor_ids[boundary]
522
523
for j in eachnode(dg), i in eachnode(dg)
524
# Get parabolic boundary fluxes
525
flux_ll, flux_rr = get_surface_node_vars(u, equations_parabolic, dg,
526
i, j, boundary)
527
if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right
528
flux_inner = flux_ll
529
else # Element is on the right, boundary on the left
530
flux_inner = flux_rr
531
end
532
533
x = get_node_coords(node_coordinates, equations_parabolic, dg,
534
i, j, boundary)
535
536
# TODO: add a field in `cache.boundaries` for gradient information. UPDATE THIS COMMENT
537
# Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information.
538
# This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion3D and
539
# NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion3D as of 2022-6-27.
540
# It will not work with implementations which utilize `u_inner` to impose boundary conditions, such as
541
# the `Slip` boundary condition, which can be imposed to realize reflective or symmetric boundaries.
542
flux = boundary_condition(flux_inner, nothing,
543
get_unsigned_normal_vector_3d(direction),
544
x, t, Divergence(), equations_parabolic)
545
546
# Copy flux to left and right element storage
547
for v in eachvariable(equations_parabolic)
548
surface_flux_values[v, i, j, direction, neighbor] = flux[v]
549
end
550
end
551
end
552
553
return nothing
554
end
555
556
# Specialization `flux_parabolic::Tuple` needed to
557
# avoid amibiguity with the hyperbolic version of `prolong2mortars!` in dg_3d.jl
558
# which is for the variables itself, i.e., `u::Array{uEltype, 5}`.
559
function prolong2mortars!(cache, flux_parabolic::Tuple,
560
mesh::TreeMesh{3},
561
equations_parabolic::AbstractEquationsParabolic,
562
mortar_l2::LobattoLegendreMortarL2, dg::DGSEM)
563
# temporary buffer for projections
564
@unpack fstar_tmp1_threaded = cache
565
566
flux_parabolic_x, flux_parabolic_y, flux_parabolic_z = flux_parabolic
567
@threaded for mortar in eachmortar(dg, cache)
568
fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()]
569
570
lower_left_element = cache.mortars.neighbor_ids[1, mortar]
571
lower_right_element = cache.mortars.neighbor_ids[2, mortar]
572
upper_left_element = cache.mortars.neighbor_ids[3, mortar]
573
upper_right_element = cache.mortars.neighbor_ids[4, mortar]
574
large_element = cache.mortars.neighbor_ids[5, mortar]
575
576
# Copy solution small to small
577
if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side
578
if cache.mortars.orientations[mortar] == 1
579
# L2 mortars in x-direction
580
for k in eachnode(dg), j in eachnode(dg)
581
for v in eachvariable(equations_parabolic)
582
cache.mortars.u_upper_left[2, v, j, k, mortar] = flux_parabolic_x[v,
583
1,
584
j,
585
k,
586
upper_left_element]
587
cache.mortars.u_upper_right[2, v, j, k, mortar] = flux_parabolic_x[v,
588
1,
589
j,
590
k,
591
upper_right_element]
592
cache.mortars.u_lower_left[2, v, j, k, mortar] = flux_parabolic_x[v,
593
1,
594
j,
595
k,
596
lower_left_element]
597
cache.mortars.u_lower_right[2, v, j, k, mortar] = flux_parabolic_x[v,
598
1,
599
j,
600
k,
601
lower_right_element]
602
end
603
end
604
elseif cache.mortars.orientations[mortar] == 2
605
# L2 mortars in y-direction
606
for k in eachnode(dg), i in eachnode(dg)
607
for v in eachvariable(equations_parabolic)
608
cache.mortars.u_upper_left[2, v, i, k, mortar] = flux_parabolic_y[v,
609
i,
610
1,
611
k,
612
upper_left_element]
613
cache.mortars.u_upper_right[2, v, i, k, mortar] = flux_parabolic_y[v,
614
i,
615
1,
616
k,
617
upper_right_element]
618
cache.mortars.u_lower_left[2, v, i, k, mortar] = flux_parabolic_y[v,
619
i,
620
1,
621
k,
622
lower_left_element]
623
cache.mortars.u_lower_right[2, v, i, k, mortar] = flux_parabolic_y[v,
624
i,
625
1,
626
k,
627
lower_right_element]
628
end
629
end
630
else # orientations[mortar] == 3
631
# L2 mortars in z-direction
632
for j in eachnode(dg), i in eachnode(dg)
633
for v in eachvariable(equations_parabolic)
634
cache.mortars.u_upper_left[2, v, i, j, mortar] = flux_parabolic_z[v,
635
i,
636
j,
637
1,
638
upper_left_element]
639
cache.mortars.u_upper_right[2, v, i, j, mortar] = flux_parabolic_z[v,
640
i,
641
j,
642
1,
643
upper_right_element]
644
cache.mortars.u_lower_left[2, v, i, j, mortar] = flux_parabolic_z[v,
645
i,
646
j,
647
1,
648
lower_left_element]
649
cache.mortars.u_lower_right[2, v, i, j, mortar] = flux_parabolic_z[v,
650
i,
651
j,
652
1,
653
lower_right_element]
654
end
655
end
656
end
657
else # large_sides[mortar] == 2 -> small elements on left side
658
if cache.mortars.orientations[mortar] == 1
659
# L2 mortars in x-direction
660
for k in eachnode(dg), j in eachnode(dg)
661
for v in eachvariable(equations_parabolic)
662
cache.mortars.u_upper_left[1, v, j, k, mortar] = flux_parabolic_x[v,
663
nnodes(dg),
664
j,
665
k,
666
upper_left_element]
667
cache.mortars.u_upper_right[1, v, j, k, mortar] = flux_parabolic_x[v,
668
nnodes(dg),
669
j,
670
k,
671
upper_right_element]
672
cache.mortars.u_lower_left[1, v, j, k, mortar] = flux_parabolic_x[v,
673
nnodes(dg),
674
j,
675
k,
676
lower_left_element]
677
cache.mortars.u_lower_right[1, v, j, k, mortar] = flux_parabolic_x[v,
678
nnodes(dg),
679
j,
680
k,
681
lower_right_element]
682
end
683
end
684
elseif cache.mortars.orientations[mortar] == 2
685
# L2 mortars in y-direction
686
for k in eachnode(dg), i in eachnode(dg)
687
for v in eachvariable(equations_parabolic)
688
cache.mortars.u_upper_left[1, v, i, k, mortar] = flux_parabolic_y[v,
689
i,
690
nnodes(dg),
691
k,
692
upper_left_element]
693
cache.mortars.u_upper_right[1, v, i, k, mortar] = flux_parabolic_y[v,
694
i,
695
nnodes(dg),
696
k,
697
upper_right_element]
698
cache.mortars.u_lower_left[1, v, i, k, mortar] = flux_parabolic_y[v,
699
i,
700
nnodes(dg),
701
k,
702
lower_left_element]
703
cache.mortars.u_lower_right[1, v, i, k, mortar] = flux_parabolic_y[v,
704
i,
705
nnodes(dg),
706
k,
707
lower_right_element]
708
end
709
end
710
else # if cache.mortars.orientations[mortar] == 3
711
# L2 mortars in z-direction
712
for j in eachnode(dg), i in eachnode(dg)
713
for v in eachvariable(equations_parabolic)
714
cache.mortars.u_upper_left[1, v, i, j, mortar] = flux_parabolic_z[v,
715
i,
716
j,
717
nnodes(dg),
718
upper_left_element]
719
cache.mortars.u_upper_right[1, v, i, j, mortar] = flux_parabolic_z[v,
720
i,
721
j,
722
nnodes(dg),
723
upper_right_element]
724
cache.mortars.u_lower_left[1, v, i, j, mortar] = flux_parabolic_z[v,
725
i,
726
j,
727
nnodes(dg),
728
lower_left_element]
729
cache.mortars.u_lower_right[1, v, i, j, mortar] = flux_parabolic_z[v,
730
i,
731
j,
732
nnodes(dg),
733
lower_right_element]
734
end
735
end
736
end
737
end
738
739
# Interpolate large element face data to small interface locations
740
if cache.mortars.large_sides[mortar] == 1 # -> large element on left side
741
leftright = 1
742
if cache.mortars.orientations[mortar] == 1
743
# L2 mortars in x-direction
744
u_large = view(flux_parabolic_x, :, nnodes(dg), :, :, large_element)
745
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
746
mortar, u_large, fstar_tmp1)
747
elseif cache.mortars.orientations[mortar] == 2
748
# L2 mortars in y-direction
749
u_large = view(flux_parabolic_y, :, :, nnodes(dg), :, large_element)
750
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
751
mortar, u_large, fstar_tmp1)
752
else # cache.mortars.orientations[mortar] == 3
753
# L2 mortars in z-direction
754
u_large = view(flux_parabolic_z, :, :, :, nnodes(dg), large_element)
755
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
756
mortar, u_large, fstar_tmp1)
757
end
758
else # large_sides[mortar] == 2 -> large element on right side
759
leftright = 2
760
if cache.mortars.orientations[mortar] == 1
761
# L2 mortars in x-direction
762
u_large = view(flux_parabolic_x, :, 1, :, :, large_element)
763
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
764
mortar, u_large, fstar_tmp1)
765
elseif cache.mortars.orientations[mortar] == 2
766
# L2 mortars in y-direction
767
u_large = view(flux_parabolic_y, :, :, 1, :, large_element)
768
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
769
mortar, u_large, fstar_tmp1)
770
else # cache.mortars.orientations[mortar] == 3
771
# L2 mortars in z-direction
772
u_large = view(flux_parabolic_z, :, :, :, 1, large_element)
773
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
774
mortar, u_large, fstar_tmp1)
775
end
776
end
777
end
778
779
return nothing
780
end
781
782
# NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms.
783
# Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for
784
# hyperbolic terms with conserved terms only, i.e., no nonconservative terms.
785
function calc_mortar_flux!(surface_flux_values, mesh::TreeMesh{3},
786
equations_parabolic::AbstractEquationsParabolic,
787
mortar_l2::LobattoLegendreMortarL2, surface_integral,
788
dg::DG, parabolic_scheme, gradient_or_divergence, cache)
789
@unpack surface_flux = surface_integral
790
@unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations = cache.mortars
791
@unpack (fstar_primary_upper_left_threaded, fstar_primary_upper_right_threaded,
792
fstar_primary_lower_left_threaded, fstar_primary_lower_right_threaded,
793
fstar_tmp1_threaded) = cache
794
795
@threaded for mortar in eachmortar(dg, cache)
796
# Choose thread-specific pre-allocated container
797
fstar_upper_left = fstar_primary_upper_left_threaded[Threads.threadid()]
798
fstar_upper_right = fstar_primary_upper_right_threaded[Threads.threadid()]
799
fstar_lower_left = fstar_primary_lower_left_threaded[Threads.threadid()]
800
fstar_lower_right = fstar_primary_lower_right_threaded[Threads.threadid()]
801
fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()]
802
803
# Calculate fluxes
804
orientation = orientations[mortar]
805
calc_fstar!(fstar_upper_left, mesh, equations_parabolic,
806
surface_flux, dg,
807
parabolic_scheme, gradient_or_divergence,
808
u_upper_left, mortar, orientation)
809
calc_fstar!(fstar_upper_right, mesh, equations_parabolic,
810
surface_flux, dg,
811
parabolic_scheme, gradient_or_divergence,
812
u_upper_right, mortar, orientation)
813
calc_fstar!(fstar_lower_left, mesh, equations_parabolic,
814
surface_flux, dg,
815
parabolic_scheme, gradient_or_divergence,
816
u_lower_left, mortar, orientation)
817
calc_fstar!(fstar_lower_right, mesh, equations_parabolic,
818
surface_flux, dg,
819
parabolic_scheme, gradient_or_divergence,
820
u_lower_right, mortar, orientation)
821
822
mortar_fluxes_to_elements!(surface_flux_values,
823
mesh, equations_parabolic, mortar_l2, dg, cache,
824
mortar, fstar_upper_left, fstar_upper_right,
825
fstar_lower_left, fstar_lower_right,
826
fstar_tmp1)
827
end
828
829
return nothing
830
end
831
832
@inline function calc_fstar!(destination::AbstractArray{<:Any, 3},
833
mesh, equations_parabolic::AbstractEquationsParabolic,
834
surface_flux, dg::DGSEM,
835
parabolic_scheme, gradient_or_divergence,
836
u_interfaces, interface, orientation)
837
for j in eachnode(dg), i in eachnode(dg)
838
# Call pointwise two-point numerical flux function
839
u_ll, u_rr = get_surface_node_vars(u_interfaces, equations_parabolic, dg,
840
i, j, interface)
841
842
flux = flux_parabolic(u_ll, u_rr, gradient_or_divergence,
843
equations_parabolic, parabolic_scheme)
844
845
# Copy flux to left and right element storage
846
set_node_vars!(destination, flux, equations_parabolic, dg, i, j)
847
end
848
849
return nothing
850
end
851
852
@inline function mortar_fluxes_to_elements!(surface_flux_values,
853
mesh::TreeMesh{3},
854
equations_parabolic::AbstractEquationsParabolic,
855
mortar_l2::LobattoLegendreMortarL2,
856
dg::DGSEM, cache,
857
mortar,
858
fstar_upper_left, fstar_upper_right,
859
fstar_lower_left, fstar_lower_right,
860
fstar_tmp1)
861
lower_left_element = cache.mortars.neighbor_ids[1, mortar]
862
lower_right_element = cache.mortars.neighbor_ids[2, mortar]
863
upper_left_element = cache.mortars.neighbor_ids[3, mortar]
864
upper_right_element = cache.mortars.neighbor_ids[4, mortar]
865
large_element = cache.mortars.neighbor_ids[5, mortar]
866
867
# Copy flux small to small
868
if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side
869
if cache.mortars.orientations[mortar] == 1
870
# L2 mortars in x-direction
871
direction = 1
872
elseif cache.mortars.orientations[mortar] == 2
873
# L2 mortars in y-direction
874
direction = 3
875
else # if cache.mortars.orientations[mortar] == 3
876
# L2 mortars in z-direction
877
direction = 5
878
end
879
else # large_sides[mortar] == 2 -> small elements on left side
880
if cache.mortars.orientations[mortar] == 1
881
# L2 mortars in x-direction
882
direction = 2
883
elseif cache.mortars.orientations[mortar] == 2
884
# L2 mortars in y-direction
885
direction = 4
886
else # if cache.mortars.orientations[mortar] == 3
887
# L2 mortars in z-direction
888
direction = 6
889
end
890
end
891
surface_flux_values[:, :, :, direction, upper_left_element] .= fstar_upper_left
892
surface_flux_values[:, :, :, direction, upper_right_element] .= fstar_upper_right
893
surface_flux_values[:, :, :, direction, lower_left_element] .= fstar_lower_left
894
surface_flux_values[:, :, :, direction, lower_right_element] .= fstar_lower_right
895
896
# Project small fluxes to large element
897
if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side
898
if cache.mortars.orientations[mortar] == 1
899
# L2 mortars in x-direction
900
direction = 2
901
elseif cache.mortars.orientations[mortar] == 2
902
# L2 mortars in y-direction
903
direction = 4
904
else # if cache.mortars.orientations[mortar] == 3
905
# L2 mortars in z-direction
906
direction = 6
907
end
908
else # large_sides[mortar] == 2 -> small elements on left side
909
if cache.mortars.orientations[mortar] == 1
910
# L2 mortars in x-direction
911
direction = 1
912
elseif cache.mortars.orientations[mortar] == 2
913
# L2 mortars in y-direction
914
direction = 3
915
else # if cache.mortars.orientations[mortar] == 3
916
# L2 mortars in z-direction
917
direction = 5
918
end
919
end
920
921
multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction,
922
large_element),
923
mortar_l2.reverse_lower, mortar_l2.reverse_upper,
924
fstar_upper_left, fstar_tmp1)
925
add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction,
926
large_element),
927
mortar_l2.reverse_upper, mortar_l2.reverse_upper,
928
fstar_upper_right, fstar_tmp1)
929
add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction,
930
large_element),
931
mortar_l2.reverse_lower, mortar_l2.reverse_lower,
932
fstar_lower_left, fstar_tmp1)
933
add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction,
934
large_element),
935
mortar_l2.reverse_upper, mortar_l2.reverse_lower,
936
fstar_lower_right, fstar_tmp1)
937
938
return nothing
939
end
940
941
function calc_volume_integral_gradient!(gradients, u_transformed,
942
mesh::TreeMesh{3}, # for dispatch only
943
equations_parabolic::AbstractEquationsParabolic,
944
dg::DGSEM, cache)
945
@unpack derivative_hat = dg.basis
946
gradients_x, gradients_y, gradients_z = gradients
947
948
@threaded for element in eachelement(dg, cache)
949
# Calculate volume terms in one element,
950
# corresponds to `kernel` functions for the hyperbolic part of the flux
951
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
952
u_node = get_node_vars(u_transformed, equations_parabolic, dg,
953
i, j, k, element)
954
955
for ii in eachnode(dg)
956
multiply_add_to_node_vars!(gradients_x, derivative_hat[ii, i],
957
u_node, equations_parabolic, dg,
958
ii, j, k, element)
959
end
960
961
for jj in eachnode(dg)
962
multiply_add_to_node_vars!(gradients_y, derivative_hat[jj, j],
963
u_node, equations_parabolic, dg,
964
i, jj, k, element)
965
end
966
967
for kk in eachnode(dg)
968
multiply_add_to_node_vars!(gradients_z, derivative_hat[kk, k],
969
u_node, equations_parabolic, dg,
970
i, j, kk, element)
971
end
972
end
973
end
974
975
return nothing
976
end
977
978
function calc_interface_flux_gradient!(surface_flux_values,
979
mesh::TreeMesh{3},
980
equations_parabolic,
981
dg::DG, parabolic_scheme, cache)
982
@unpack neighbor_ids, orientations = cache.interfaces
983
984
@threaded for interface in eachinterface(dg, cache)
985
# Get neighboring elements
986
left_id = neighbor_ids[1, interface]
987
right_id = neighbor_ids[2, interface]
988
989
# Determine interface direction with respect to elements:
990
# orientation = 1: left -> 2, right -> 1
991
# orientation = 2: left -> 4, right -> 3
992
# orientation = 3: left -> 6, right -> 5
993
left_direction = 2 * orientations[interface]
994
right_direction = 2 * orientations[interface] - 1
995
996
for j in eachnode(dg), i in eachnode(dg)
997
# Call pointwise Riemann solver
998
u_ll, u_rr = get_surface_node_vars(cache.interfaces.u,
999
equations_parabolic, dg,
1000
i, j, interface)
1001
1002
flux = flux_parabolic(u_ll, u_rr, Gradient(),
1003
equations_parabolic, parabolic_scheme)
1004
1005
# Copy flux to left and right element storage
1006
for v in eachvariable(equations_parabolic)
1007
surface_flux_values[v, i, j, left_direction, left_id] = flux[v]
1008
surface_flux_values[v, i, j, right_direction, right_id] = flux[v]
1009
end
1010
end
1011
end
1012
1013
return nothing
1014
end
1015
1016
function calc_surface_integral_gradient!(gradients,
1017
mesh::TreeMesh{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
1023
gradients_x, gradients_y, gradients_z = gradients
1024
1025
# Note that all fluxes have been computed with outward-pointing normal vectors.
1026
# We also use explicit assignments instead of `+=` to let `@muladd` turn these
1027
# into FMAs (see comment at the top of the file).
1028
factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±1
1029
@threaded for element in eachelement(dg, cache)
1030
for m in eachnode(dg), l in eachnode(dg)
1031
for v in eachvariable(equations_parabolic)
1032
# surface at -x
1033
gradients_x[v, 1, l, m, element] = (gradients_x[v,
1034
1,
1035
l,
1036
m,
1037
element] -
1038
surface_flux_values[v,
1039
l,
1040
m,
1041
1,
1042
element] *
1043
factor)
1044
1045
# surface at +x
1046
gradients_x[v, nnodes(dg), l, m, element] = (gradients_x[v,
1047
nnodes(dg),
1048
l,
1049
m,
1050
element] +
1051
surface_flux_values[v,
1052
l,
1053
m,
1054
2,
1055
element] *
1056
factor)
1057
1058
# surface at -y
1059
gradients_y[v, l, 1, m, element] = (gradients_y[v,
1060
l,
1061
1,
1062
m,
1063
element] -
1064
surface_flux_values[v,
1065
l,
1066
m,
1067
3,
1068
element] *
1069
factor)
1070
1071
# surface at +y
1072
gradients_y[v, l, nnodes(dg), m, element] = (gradients_y[v,
1073
l,
1074
nnodes(dg),
1075
m,
1076
element] +
1077
surface_flux_values[v,
1078
l,
1079
m,
1080
4,
1081
element] *
1082
factor)
1083
1084
# surface at -z
1085
gradients_z[v, l, m, 1, element] = (gradients_z[v,
1086
l,
1087
m,
1088
1,
1089
element] -
1090
surface_flux_values[v,
1091
l,
1092
m,
1093
5,
1094
element] *
1095
factor)
1096
1097
# surface at +z
1098
gradients_z[v, l, m, nnodes(dg), element] = (gradients_z[v,
1099
l,
1100
m,
1101
nnodes(dg),
1102
element] +
1103
surface_flux_values[v,
1104
l,
1105
m,
1106
6,
1107
element] *
1108
factor)
1109
end
1110
end
1111
end
1112
1113
return nothing
1114
end
1115
1116
function apply_jacobian_parabolic!(gradients::NTuple{3}, mesh::AbstractMesh{3},
1117
equations_parabolic::AbstractEquationsParabolic,
1118
dg::DG, cache)
1119
gradients_x, gradients_y, gradients_z = gradients
1120
1121
apply_jacobian_parabolic!(gradients_x, mesh, equations_parabolic, dg,
1122
cache)
1123
apply_jacobian_parabolic!(gradients_y, mesh, equations_parabolic, dg,
1124
cache)
1125
apply_jacobian_parabolic!(gradients_z, mesh, equations_parabolic, dg,
1126
cache)
1127
1128
return nothing
1129
end
1130
1131
# Needed to *not* flip the sign of the inverse Jacobian.
1132
# This is because the parabolic fluxes are assumed to be of the form
1133
# `du/dt + df/dx = dg/dx + source(x,t)`,
1134
# where f(u) is the inviscid flux and g(u) is the parabolic flux.
1135
function apply_jacobian_parabolic!(du::AbstractArray, mesh::TreeMesh{3},
1136
equations_parabolic::AbstractEquationsParabolic,
1137
dg::DG, cache)
1138
@unpack inverse_jacobian = cache.elements
1139
1140
@threaded for element in eachelement(dg, cache)
1141
factor = inverse_jacobian[element]
1142
1143
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
1144
for v in eachvariable(equations_parabolic)
1145
du[v, i, j, k, element] *= factor
1146
end
1147
end
1148
end
1149
1150
return nothing
1151
end
1152
1153
# Need dimension specific version to avoid error at dispatching
1154
function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic::Nothing,
1155
equations_parabolic::AbstractEquations{3}, dg::DG,
1156
cache)
1157
return nothing
1158
end
1159
1160
function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,
1161
equations_parabolic::AbstractEquations{3}, dg::DG,
1162
cache)
1163
@unpack node_coordinates = cache.elements
1164
1165
@threaded for element in eachelement(dg, cache)
1166
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
1167
u_local = get_node_vars(u, equations_parabolic, dg, i, j, k, element)
1168
gradients_x_local = get_node_vars(gradients[1], equations_parabolic, dg,
1169
i, j, k, element)
1170
gradients_y_local = get_node_vars(gradients[2], equations_parabolic, dg,
1171
i, j, k, element)
1172
gradients_z_local = get_node_vars(gradients[3], equations_parabolic, dg,
1173
i, j, k, element)
1174
gradients_local = (gradients_x_local, gradients_y_local, gradients_z_local)
1175
x_local = get_node_coords(node_coordinates, equations_parabolic, dg,
1176
i, j, k, element)
1177
du_local = source_terms_parabolic(u_local, gradients_local, x_local, t,
1178
equations_parabolic)
1179
add_to_node_vars!(du, du_local, equations_parabolic, dg, i, j, k, element)
1180
end
1181
end
1182
1183
return nothing
1184
end
1185
end # @muladd
1186
1187