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