Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgmulti/dg_parabolic.jl
5591 views
1
# version for standard (e.g., non-entropy stable or flux differencing) schemes
2
function create_cache_parabolic(mesh::DGMultiMesh,
3
equations_hyperbolic::AbstractEquations,
4
dg::DGMulti, n_elements, uEltype)
5
6
# default to taking derivatives of all hyperbolic variables
7
# TODO: parabolic; utilize the parabolic variables in `equations_parabolic` to reduce memory usage in the parabolic cache
8
nvars = nvariables(equations_hyperbolic)
9
10
(; M, Vq, Pq, Drst) = dg.basis
11
12
# gradient operators: map from nodes to quadrature
13
strong_differentiation_matrices = map(A -> Vq * A, Drst)
14
gradient_lift_matrix = Vq * dg.basis.LIFT
15
16
# divergence operators: map from quadrature to nodes
17
weak_differentiation_matrices = map(A -> (M \ (-A' * M * Pq)), Drst)
18
divergence_lift_matrix = dg.basis.LIFT
19
projection_face_interpolation_matrix = dg.basis.Vf * dg.basis.Pq
20
21
# evaluate geometric terms at quadrature points in case the mesh is curved
22
(; md) = mesh
23
J = dg.basis.Vq * md.J
24
invJ = inv.(J)
25
dxidxhatj = map(x -> dg.basis.Vq * x, md.rstxyzJ)
26
27
# u_transformed stores "transformed" variables for computing the gradient
28
u_transformed = allocate_nested_array(uEltype, nvars, size(md.x), dg)
29
gradients = SVector{ndims(mesh)}(ntuple(_ -> similar(u_transformed,
30
(dg.basis.Nq,
31
mesh.md.num_elements)),
32
ndims(mesh)))
33
flux_parabolic = similar.(gradients)
34
35
u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg)
36
scalar_flux_face_values = similar(u_face_values)
37
gradients_face_values = ntuple(_ -> similar(u_face_values), ndims(mesh))
38
39
local_u_values_threaded = [similar(u_transformed, dg.basis.Nq)
40
for _ in 1:Threads.maxthreadid()]
41
local_flux_parabolic_threaded = [SVector{ndims(mesh)}(ntuple(_ -> similar(u_transformed,
42
dg.basis.Nq),
43
ndims(mesh)))
44
for _ in 1:Threads.maxthreadid()]
45
local_flux_face_values_threaded = [similar(scalar_flux_face_values[:, 1])
46
for _ in 1:Threads.maxthreadid()]
47
48
return (; u_transformed, gradients, flux_parabolic,
49
weak_differentiation_matrices, strong_differentiation_matrices,
50
gradient_lift_matrix, projection_face_interpolation_matrix,
51
divergence_lift_matrix,
52
dxidxhatj, J, invJ, # geometric terms
53
u_face_values, gradients_face_values, scalar_flux_face_values,
54
local_u_values_threaded, local_flux_parabolic_threaded,
55
local_flux_face_values_threaded)
56
end
57
58
# Transform solution variables prior to taking the gradient
59
# (e.g., conservative to primitive variables). Defaults to doing nothing.
60
# TODO: can we avoid copying data?
61
function transform_variables!(u_transformed, u, mesh,
62
equations_parabolic::AbstractEquationsParabolic,
63
dg::DGMulti, cache)
64
transformation = gradient_variable_transformation(equations_parabolic)
65
66
@threaded for i in eachindex(u)
67
u_transformed[i] = transformation(u[i], equations_parabolic)
68
end
69
70
return nothing
71
end
72
73
# TODO: reuse entropy projection computations for DGMultiFluxDiff{<:Polynomial} (including `GaussSBP` solvers)
74
function calc_surface_integral_gradient!(gradients, u, scalar_flux_face_values,
75
mesh, equations::AbstractEquationsParabolic,
76
dg::DGMulti, cache, cache_parabolic)
77
(; gradient_lift_matrix, local_flux_face_values_threaded) = cache_parabolic
78
@threaded for e in eachelement(mesh, dg)
79
local_flux_values = local_flux_face_values_threaded[Threads.threadid()]
80
for dim in eachdim(mesh)
81
for i in eachindex(local_flux_values)
82
# compute flux * (nx, ny, nz)
83
local_flux_values[i] = scalar_flux_face_values[i, e] *
84
mesh.md.nxyzJ[dim][i, e]
85
end
86
apply_to_each_field(mul_by_accum!(gradient_lift_matrix),
87
view(gradients[dim], :, e), local_flux_values)
88
end
89
end
90
91
return nothing
92
end
93
94
function calc_volume_integral_gradient!(gradients, u, mesh::DGMultiMesh,
95
equations::AbstractEquationsParabolic,
96
dg::DGMulti, cache, cache_parabolic)
97
(; strong_differentiation_matrices) = cache_parabolic
98
99
# compute volume contributions to gradients
100
@threaded for e in eachelement(mesh, dg)
101
for i in eachdim(mesh), j in eachdim(mesh)
102
103
# We assume each element is affine (e.g., constant geometric terms) here.
104
dxidxhatj = mesh.md.rstxyzJ[i, j][1, e]
105
106
apply_to_each_field(mul_by_accum!(strong_differentiation_matrices[j],
107
dxidxhatj),
108
view(gradients[i], :, e), view(u, :, e))
109
end
110
end
111
112
return nothing
113
end
114
115
function calc_volume_integral_gradient!(gradients, u, mesh::DGMultiMesh{NDIMS, <:NonAffine},
116
equations::AbstractEquationsParabolic,
117
dg::DGMulti, cache, cache_parabolic) where {NDIMS}
118
(; strong_differentiation_matrices, dxidxhatj, local_flux_parabolic_threaded) = cache_parabolic
119
120
# compute volume contributions to gradients
121
@threaded for e in eachelement(mesh, dg)
122
123
# compute gradients with respect to reference coordinates
124
local_reference_gradients = local_flux_parabolic_threaded[Threads.threadid()]
125
for i in eachdim(mesh)
126
apply_to_each_field(mul_by!(strong_differentiation_matrices[i]),
127
local_reference_gradients[i], view(u, :, e))
128
end
129
130
# rotate to physical frame on each element
131
for i in eachdim(mesh), j in eachdim(mesh)
132
for node in eachindex(local_reference_gradients[j])
133
gradients[i][node, e] = gradients[i][node, e] +
134
dxidxhatj[i, j][node, e] *
135
local_reference_gradients[j][node]
136
end
137
end
138
end
139
140
return nothing
141
end
142
143
function calc_interface_flux_gradient!(scalar_flux_face_values,
144
mesh::DGMultiMesh, equations,
145
dg::DGMulti,
146
parabolic_scheme::ParabolicFormulationBassiRebay1,
147
cache, cache_parabolic)
148
(; u_face_values) = cache_parabolic
149
(; mapM, mapP) = mesh.md
150
@threaded for face_node_index in each_face_node_global(mesh, dg)
151
idM, idP = mapM[face_node_index], mapP[face_node_index]
152
uM = u_face_values[idM]
153
uP = u_face_values[idP]
154
# Here, we use the "strong" formulation to compute the gradient.
155
# This guarantees that the parabolic formulation is symmetric and
156
# stable on curved meshes with variable geometric terms.
157
scalar_flux_face_values[idM] = 0.5f0 * (uP - uM)
158
end
159
160
return nothing
161
end
162
163
function calc_gradient!(gradients, u::StructArray, t, mesh::DGMultiMesh,
164
equations::AbstractEquationsParabolic,
165
boundary_conditions, dg::DGMulti, parabolic_scheme,
166
cache, cache_parabolic)
167
for dim in eachindex(gradients)
168
set_zero!(gradients[dim], dg)
169
end
170
171
calc_volume_integral_gradient!(gradients, u, mesh, equations, dg, cache,
172
cache_parabolic)
173
174
# prolong to interfaces
175
(; u_face_values) = cache_parabolic
176
apply_to_each_field(mul_by!(dg.basis.Vf), u_face_values, u)
177
178
# compute fluxes at interfaces
179
(; scalar_flux_face_values) = cache_parabolic
180
calc_interface_flux_gradient!(scalar_flux_face_values,
181
mesh, equations, dg, parabolic_scheme, cache,
182
cache_parabolic)
183
184
calc_boundary_flux!(scalar_flux_face_values, u_face_values, t, Gradient(),
185
boundary_conditions,
186
mesh, equations, dg, cache, cache_parabolic)
187
188
# compute surface contributions
189
calc_surface_integral_gradient!(gradients, u, scalar_flux_face_values,
190
mesh, equations, dg, cache, cache_parabolic)
191
192
invert_jacobian_gradient!(gradients, mesh, equations, dg, cache, cache_parabolic)
193
194
return nothing
195
end
196
197
# affine mesh - constant Jacobian version
198
function invert_jacobian_gradient!(gradients, mesh::DGMultiMesh, equations, dg::DGMulti,
199
cache, cache_parabolic)
200
@threaded for e in eachelement(mesh, dg)
201
202
# Here, we exploit the fact that J is constant on affine elements,
203
# so we only have to access invJ once per element.
204
invJ = cache_parabolic.invJ[1, e]
205
206
for dim in eachdim(mesh)
207
for i in axes(gradients[dim], 1)
208
gradients[dim][i, e] = gradients[dim][i, e] * invJ
209
end
210
end
211
end
212
213
return nothing
214
end
215
216
# non-affine mesh - variable Jacobian version
217
function invert_jacobian_gradient!(gradients, mesh::DGMultiMesh{NDIMS, <:NonAffine},
218
equations,
219
dg::DGMulti, cache, cache_parabolic) where {NDIMS}
220
(; invJ) = cache_parabolic
221
@threaded for e in eachelement(mesh, dg)
222
for dim in eachdim(mesh)
223
for i in axes(gradients[dim], 1)
224
gradients[dim][i, e] = gradients[dim][i, e] * invJ[i, e]
225
end
226
end
227
end
228
229
return nothing
230
end
231
232
# do nothing for periodic domains
233
function calc_boundary_flux!(flux, u, t, operator_type, ::BoundaryConditionPeriodic,
234
mesh, equations::AbstractEquationsParabolic, dg::DGMulti,
235
cache, cache_parabolic)
236
return nothing
237
end
238
239
# "lispy tuple programming" instead of for loop for type stability
240
function calc_boundary_flux!(flux, u, t, operator_type, boundary_conditions,
241
mesh, equations, dg::DGMulti, cache, cache_parabolic)
242
243
# peel off first boundary condition
244
calc_single_boundary_flux!(flux, u, t, operator_type, first(boundary_conditions),
245
first(keys(boundary_conditions)),
246
mesh, equations, dg, cache, cache_parabolic)
247
248
# recurse on the remainder of the boundary conditions
249
calc_boundary_flux!(flux, u, t, operator_type, Base.tail(boundary_conditions),
250
mesh, equations, dg, cache, cache_parabolic)
251
252
return nothing
253
end
254
255
# terminate recursion
256
function calc_boundary_flux!(flux, u, t, operator_type,
257
boundary_conditions::NamedTuple{(), Tuple{}},
258
mesh, equations, dg::DGMulti, cache, cache_parabolic)
259
return nothing
260
end
261
262
function calc_single_boundary_flux!(flux_face_values, u_face_values, t,
263
operator_type, boundary_condition, boundary_key,
264
mesh, equations, dg::DGMulti{NDIMS}, cache,
265
cache_parabolic) where {NDIMS}
266
rd = dg.basis
267
md = mesh.md
268
269
num_faces = StartUpDG.num_faces(rd.element_type)
270
num_pts_per_face = rd.Nfq ÷ num_faces
271
(; xyzf, nxyz) = md
272
for f in mesh.boundary_faces[boundary_key]
273
for i in Base.OneTo(num_pts_per_face)
274
275
# reverse engineer element + face node indices (avoids reshaping arrays)
276
e = ((f - 1) ÷ num_faces) + 1
277
fid = i + ((f - 1) % num_faces) * num_pts_per_face
278
279
face_normal = SVector{NDIMS}(getindex.(nxyz, fid, e))
280
face_coordinates = SVector{NDIMS}(getindex.(xyzf, fid, e))
281
282
# for both the gradient and the divergence, the boundary flux is scalar valued.
283
# for the gradient, it is the solution; for divergence, it is the normal flux.
284
flux_face_values[fid, e] = boundary_condition(flux_face_values[fid, e],
285
u_face_values[fid, e],
286
face_normal, face_coordinates, t,
287
operator_type, equations)
288
289
# Here, we use the "strong form" for the Gradient (and the "weak form" for Divergence).
290
# `flux_face_values` should contain the boundary values for `u`, and we
291
# subtract off `u_face_values[fid, e]` because we are using the strong formulation to
292
# compute the gradient.
293
if operator_type isa Gradient
294
flux_face_values[fid, e] = flux_face_values[fid, e] - u_face_values[fid, e]
295
end
296
end
297
end
298
return nothing
299
end
300
301
function calc_parabolic_fluxes!(flux_parabolic, u, gradients, mesh::DGMultiMesh,
302
equations::AbstractEquationsParabolic,
303
dg::DGMulti, cache, cache_parabolic)
304
for dim in eachdim(mesh)
305
set_zero!(flux_parabolic[dim], dg)
306
end
307
308
(; local_u_values_threaded) = cache_parabolic
309
310
@threaded for e in eachelement(mesh, dg)
311
312
# reset local storage for each element, interpolate u to quadrature points
313
# TODO: DGMulti. Specialize for nodal collocation methods (SBP, GaussSBP)?
314
local_u_values = local_u_values_threaded[Threads.threadid()]
315
fill!(local_u_values, zero(eltype(local_u_values)))
316
apply_to_each_field(mul_by!(dg.basis.Vq), local_u_values, view(u, :, e))
317
318
# compute parabolic flux at quad points
319
for i in eachindex(local_u_values)
320
u_i = local_u_values[i]
321
gradients_i = getindex.(gradients, i, e)
322
for dim in eachdim(mesh)
323
flux_parabolic_i = flux(u_i, gradients_i, dim, equations)
324
setindex!(flux_parabolic[dim], flux_parabolic_i, i, e)
325
end
326
end
327
end
328
329
return nothing
330
end
331
332
# no penalization for a BR1 parabolic solver
333
function calc_parabolic_penalty!(scalar_flux_face_values, u_face_values, t,
334
boundary_conditions,
335
mesh, equations::AbstractEquationsParabolic,
336
dg::DGMulti,
337
parabolic_scheme::ParabolicFormulationBassiRebay1,
338
cache, cache_parabolic)
339
return nothing
340
end
341
342
function calc_parabolic_penalty!(scalar_flux_face_values, u_face_values, t,
343
boundary_conditions, mesh,
344
equations::AbstractEquationsParabolic,
345
dg::DGMulti, parabolic_scheme, cache, cache_parabolic)
346
# compute fluxes at interfaces
347
(; scalar_flux_face_values) = cache_parabolic
348
(; mapM, mapP) = mesh.md
349
@threaded for face_node_index in each_face_node_global(mesh, dg)
350
idM, idP = mapM[face_node_index], mapP[face_node_index]
351
uM, uP = u_face_values[idM], u_face_values[idP]
352
scalar_flux_face_values[idM] = scalar_flux_face_values[idM] +
353
penalty(uP, uM, equations, parabolic_scheme)
354
end
355
return nothing
356
end
357
358
function calc_volume_integral_divergence!(du, u, flux_parabolic, mesh::DGMultiMesh,
359
equations::AbstractEquationsParabolic,
360
dg::DGMulti, cache, cache_parabolic)
361
(; weak_differentiation_matrices) = cache_parabolic
362
363
# compute volume contributions to divergence
364
@threaded for e in eachelement(mesh, dg)
365
for i in eachdim(mesh), j in eachdim(mesh)
366
dxidxhatj = mesh.md.rstxyzJ[i, j][1, e] # assumes mesh is affine
367
apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j], dxidxhatj),
368
view(du, :, e), view(flux_parabolic[i], :, e))
369
end
370
end
371
372
return nothing
373
end
374
375
function calc_volume_integral_divergence!(du, u, flux_parabolic,
376
mesh::DGMultiMesh{NDIMS, <:NonAffine},
377
equations::AbstractEquationsParabolic,
378
dg::DGMulti, cache, cache_parabolic) where {NDIMS}
379
(; weak_differentiation_matrices, dxidxhatj, local_flux_parabolic_threaded) = cache_parabolic
380
381
# compute volume contributions to divergence
382
@threaded for e in eachelement(mesh, dg)
383
local_parabolic_flux = local_flux_parabolic_threaded[Threads.threadid()][1]
384
for i in eachdim(mesh)
385
# rotate flux to reference coordinates
386
fill!(local_parabolic_flux, zero(eltype(local_parabolic_flux)))
387
for j in eachdim(mesh)
388
for node in eachindex(local_parabolic_flux)
389
local_parabolic_flux[node] = local_parabolic_flux[node] +
390
dxidxhatj[j, i][node, e] *
391
flux_parabolic[j][node, e]
392
end
393
end
394
395
# differentiate with respect to reference coordinates
396
apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[i]),
397
view(du, :, e), local_parabolic_flux)
398
end
399
end
400
401
return nothing
402
end
403
404
function calc_interface_flux_divergence!(scalar_flux_face_values,
405
mesh, equations, dg,
406
parabolic_scheme::ParabolicFormulationBassiRebay1,
407
cache, cache_parabolic)
408
flux_parabolic_face_values = cache_parabolic.gradients_face_values # reuse storage
409
(; mapM, mapP, nxyzJ) = mesh.md
410
411
@threaded for face_node_index in each_face_node_global(mesh, dg, cache, cache_parabolic)
412
idM, idP = mapM[face_node_index], mapP[face_node_index]
413
414
# compute f(u, ∇u) ⋅ n
415
flux_face_value = zero(eltype(scalar_flux_face_values))
416
for dim in eachdim(mesh)
417
fM = flux_parabolic_face_values[dim][idM]
418
fP = flux_parabolic_face_values[dim][idP]
419
# Here, we use the "weak" formulation to compute the divergence (to ensure stability on curved meshes).
420
flux_face_value = flux_face_value +
421
0.5f0 * (fP + fM) * nxyzJ[dim][face_node_index]
422
end
423
scalar_flux_face_values[idM] = flux_face_value
424
end
425
426
return nothing
427
end
428
429
function calc_divergence!(du, u::StructArray, t, flux_parabolic, mesh::DGMultiMesh,
430
equations::AbstractEquationsParabolic,
431
boundary_conditions, dg::DGMulti, parabolic_scheme, cache,
432
cache_parabolic)
433
set_zero!(du, dg)
434
435
calc_volume_integral_divergence!(du, u, flux_parabolic, mesh, equations, dg, cache,
436
cache_parabolic)
437
438
# interpolates from solution coefficients to face quadrature points
439
(; projection_face_interpolation_matrix) = cache_parabolic
440
flux_parabolic_face_values = cache_parabolic.gradients_face_values # reuse storage
441
for dim in eachdim(mesh)
442
apply_to_each_field(mul_by!(projection_face_interpolation_matrix),
443
flux_parabolic_face_values[dim], flux_parabolic[dim])
444
end
445
446
# compute fluxes at interfaces
447
(; scalar_flux_face_values) = cache_parabolic
448
calc_interface_flux_divergence!(scalar_flux_face_values,
449
mesh, equations, dg, parabolic_scheme, cache,
450
cache_parabolic)
451
452
calc_boundary_flux!(scalar_flux_face_values, cache_parabolic.u_face_values, t,
453
Divergence(),
454
boundary_conditions, mesh, equations, dg, cache, cache_parabolic)
455
456
calc_parabolic_penalty!(scalar_flux_face_values, cache_parabolic.u_face_values, t,
457
boundary_conditions, mesh, equations, dg, parabolic_scheme,
458
cache, cache_parabolic)
459
460
# surface contributions
461
apply_to_each_field(mul_by_accum!(cache_parabolic.divergence_lift_matrix), du,
462
scalar_flux_face_values)
463
464
return nothing
465
end
466
467
# assumptions: parabolic terms are of the form div(f(u, grad(u))) and
468
# will be discretized first order form as follows:
469
# 1. compute grad(u)
470
# 2. compute f(u, grad(u))
471
# 3. compute div(u)
472
# boundary conditions will be applied to both grad(u) and div(u).
473
function rhs_parabolic!(du, u, t, mesh::DGMultiMesh,
474
equations_parabolic::AbstractEquationsParabolic,
475
boundary_conditions, source_terms_parabolic,
476
dg::DGMulti, parabolic_scheme, cache, cache_parabolic)
477
set_zero!(du, dg)
478
479
@trixi_timeit timer() "transform variables" begin
480
(; u_transformed, gradients, flux_parabolic) = cache_parabolic
481
transform_variables!(u_transformed, u, mesh, equations_parabolic,
482
dg, cache)
483
end
484
485
@trixi_timeit timer() "calc gradient" begin
486
calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic,
487
boundary_conditions, dg, parabolic_scheme, cache, cache_parabolic)
488
end
489
490
@trixi_timeit timer() "calc parabolic fluxes" begin
491
calc_parabolic_fluxes!(flux_parabolic, u_transformed, gradients,
492
mesh, equations_parabolic, dg, cache, cache_parabolic)
493
end
494
495
@trixi_timeit timer() "calc divergence" begin
496
calc_divergence!(du, u_transformed, t, flux_parabolic, mesh, equations_parabolic,
497
boundary_conditions, dg, parabolic_scheme, cache, cache_parabolic)
498
end
499
500
@trixi_timeit timer() "jacobian" begin
501
# Note: we do not flip the sign of the geometric Jacobian here.
502
# This is because the parabolic fluxes are assumed to be of the form
503
# `du/dt + df/dx = dg/dx + source(x,t)`,
504
# where f(u) is the inviscid flux and g(u) is the parabolic flux.
505
invert_jacobian!(du, mesh, equations_parabolic, dg, cache; scaling = 1)
506
end
507
508
@trixi_timeit timer() "source terms parabolic" begin
509
calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic, mesh,
510
equations_parabolic, dg, cache)
511
end
512
513
return nothing
514
end
515
516
# Multiple calc_sources_parabolic! to resolve method ambiguities
517
function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic::Nothing,
518
mesh, equations_parabolic,
519
dg::Union{<:DGMulti, <:DGMultiFluxDiffSBP}, cache)
520
return nothing
521
end
522
523
# uses quadrature + projection to compute source terms.
524
function calc_sources_parabolic!(du, u, gradients, t, source_terms_parabolic,
525
mesh, equations_parabolic, dg::DGMulti, cache)
526
md = mesh.md
527
@threaded for e in eachelement(mesh, dg, cache)
528
for i in eachnode(dg)
529
du[i, e] = du[i, e] +
530
source_terms_parabolic(u[i, e], SVector(getindex.(gradients, i, e)),
531
SVector(getindex.(md.xyzq, i, e)),
532
t, equations_parabolic)
533
end
534
end
535
536
return nothing
537
end
538
539