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