Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem/basis_lobatto_legendre.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
"""
9
LobattoLegendreBasis([RealT = Float64,] polydeg::Integer)
10
11
Create a nodal Lobatto-Legendre basis for polynomials of degree `polydeg`.
12
13
For the special case `polydeg=0` the DG method reduces to a finite volume method.
14
Therefore, this function sets the center point of the cell as single node.
15
This exceptional case is currently only supported for TreeMesh!
16
"""
17
struct LobattoLegendreBasis{RealT <: Real, NNODES,
18
VectorT <: AbstractVector{RealT},
19
InverseVandermondeLegendre <: AbstractMatrix{RealT},
20
DerivativeMatrix <: AbstractMatrix{RealT}} <:
21
AbstractBasisSBP{RealT}
22
nodes::VectorT
23
weights::VectorT
24
inverse_weights::VectorT
25
26
inverse_vandermonde_legendre::InverseVandermondeLegendre
27
28
derivative_matrix::DerivativeMatrix # strong form derivative matrix "D"
29
derivative_split::DerivativeMatrix # strong form derivative matrix minus boundary terms
30
derivative_hat::DerivativeMatrix # weak form matrix "Dhat", negative adjoint wrt the SBP dot product
31
end
32
33
function Adapt.adapt_structure(to, basis::LobattoLegendreBasis)
34
inverse_vandermonde_legendre = adapt(to, basis.inverse_vandermonde_legendre)
35
RealT = eltype(inverse_vandermonde_legendre)
36
37
nodes = SVector{<:Any, RealT}(basis.nodes)
38
weights = SVector{<:Any, RealT}(basis.weights)
39
inverse_weights = SVector{<:Any, RealT}(basis.inverse_weights)
40
derivative_matrix = adapt(to, basis.derivative_matrix)
41
derivative_split = adapt(to, basis.derivative_split)
42
derivative_hat = adapt(to, basis.derivative_hat)
43
return LobattoLegendreBasis{RealT, nnodes(basis), typeof(nodes),
44
typeof(inverse_vandermonde_legendre),
45
typeof(derivative_matrix)}(nodes,
46
weights,
47
inverse_weights,
48
inverse_vandermonde_legendre,
49
derivative_matrix,
50
derivative_split,
51
derivative_hat)
52
end
53
54
function LobattoLegendreBasis(RealT, polydeg::Integer)
55
nnodes_ = polydeg + 1
56
57
nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_, RealT)
58
inverse_weights_ = inv.(weights_)
59
60
_, inverse_vandermonde_legendre = vandermonde_legendre(nodes_, RealT)
61
62
derivative_matrix = polynomial_derivative_matrix(nodes_)
63
derivative_split = calc_Dsplit(derivative_matrix, weights_)
64
derivative_hat = calc_Dhat(derivative_matrix, weights_)
65
66
# Type conversions to enable possible optimizations of runtime performance
67
# and latency
68
nodes = SVector{nnodes_, RealT}(nodes_)
69
weights = SVector{nnodes_, RealT}(weights_)
70
inverse_weights = SVector{nnodes_, RealT}(inverse_weights_)
71
72
# We keep the matrices above stored using the standard `Matrix` type
73
# since this is usually as fast as `SMatrix`
74
# (when using `let` in the volume integral/`@threaded`)
75
# and reduces latency
76
77
return LobattoLegendreBasis{RealT, nnodes_, typeof(nodes),
78
typeof(inverse_vandermonde_legendre),
79
typeof(derivative_matrix)}(nodes, weights,
80
inverse_weights,
81
inverse_vandermonde_legendre,
82
derivative_matrix,
83
derivative_split,
84
derivative_hat)
85
end
86
LobattoLegendreBasis(polydeg::Integer) = LobattoLegendreBasis(Float64, polydeg)
87
88
function Base.show(io::IO, basis::LobattoLegendreBasis)
89
@nospecialize basis # reduce precompilation time
90
91
print(io, "LobattoLegendreBasis{", real(basis), "}(polydeg=", polydeg(basis), ")")
92
return nothing
93
end
94
function Base.show(io::IO, ::MIME"text/plain", basis::LobattoLegendreBasis)
95
@nospecialize basis # reduce precompilation time
96
97
print(io, "LobattoLegendreBasis{", real(basis), "} with polynomials of degree ",
98
polydeg(basis))
99
return nothing
100
end
101
102
function Base.:(==)(b1::LobattoLegendreBasis, b2::LobattoLegendreBasis)
103
if typeof(b1) != typeof(b2)
104
return false
105
end
106
107
for field in fieldnames(typeof(b1))
108
if getfield(b1, field) != getfield(b2, field)
109
return false
110
end
111
end
112
113
return true
114
end
115
116
@inline Base.real(basis::LobattoLegendreBasis{RealT}) where {RealT} = RealT
117
118
@inline function nnodes(basis::LobattoLegendreBasis{RealT, NNODES}) where {RealT, NNODES
119
}
120
return NNODES
121
end
122
123
"""
124
eachnode(basis::LobattoLegendreBasis)
125
126
Return an iterator over the indices that specify the location in relevant data structures
127
for the nodes in `basis`.
128
In particular, not the nodes themselves are returned.
129
"""
130
@inline eachnode(basis::LobattoLegendreBasis) = Base.OneTo(nnodes(basis))
131
132
@inline polydeg(basis::LobattoLegendreBasis) = nnodes(basis) - 1
133
134
@inline get_nodes(basis::LobattoLegendreBasis) = basis.nodes
135
136
"""
137
integrate(f, u, basis::LobattoLegendreBasis)
138
139
Map the function `f` to the coefficients `u` and integrate with respect to the
140
quadrature rule given by `basis`.
141
"""
142
function integrate(f, u, basis::LobattoLegendreBasis)
143
@unpack weights = basis
144
145
res = zero(f(first(u)))
146
for i in eachindex(u, weights)
147
res += f(u[i]) * weights[i]
148
end
149
return res
150
end
151
152
# Return the first/last weight of the quadrature associated with `basis`.
153
# Since the mass matrix for nodal Lobatto-Legendre bases is diagonal,
154
# these weights are the only coefficients necessary for the scaling of
155
# surface terms/integrals in DGSEM.
156
left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights)
157
right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights)
158
159
struct LobattoLegendreMortarL2{RealT <: Real, NNODES,
160
ForwardMatrix <: AbstractMatrix{RealT},
161
ReverseMatrix <: AbstractMatrix{RealT}} <:
162
AbstractMortarL2{RealT}
163
forward_upper::ForwardMatrix
164
forward_lower::ForwardMatrix
165
reverse_upper::ReverseMatrix
166
reverse_lower::ReverseMatrix
167
end
168
169
function Adapt.adapt_structure(to, mortar::LobattoLegendreMortarL2)
170
forward_upper = adapt(to, mortar.forward_upper)
171
forward_lower = adapt(to, mortar.forward_lower)
172
reverse_upper = adapt(to, mortar.reverse_upper)
173
reverse_lower = adapt(to, mortar.reverse_lower)
174
return LobattoLegendreMortarL2{eltype(forward_upper), nnodes(mortar),
175
typeof(forward_upper),
176
typeof(reverse_upper)}(forward_upper, forward_lower,
177
reverse_upper, reverse_lower)
178
end
179
180
function MortarL2(basis::LobattoLegendreBasis)
181
RealT = real(basis)
182
nnodes_ = nnodes(basis)
183
184
forward_upper = calc_forward_upper(nnodes_, RealT)
185
forward_lower = calc_forward_lower(nnodes_, RealT)
186
reverse_upper = calc_reverse_upper(nnodes_, Val(:gauss), RealT)
187
reverse_lower = calc_reverse_lower(nnodes_, Val(:gauss), RealT)
188
189
# We keep the matrices above stored using the standard `Matrix` type
190
# since this is usually as fast as `SMatrix`
191
# (when using `let` in the volume integral/`@threaded`)
192
# and reduces latency
193
194
# TODO: Taal performance
195
# Check the performance of different implementations of `mortar_fluxes_to_elements!`
196
# with different types of the reverse matrices and different types of
197
# `fstar_upper_threaded` etc. used in the cache.
198
# Check whether `@turbo` with `eachnode` in `multiply_dimensionwise!` can be faster than
199
# `@tullio` when the matrix sizes are not necessarily static.
200
# reverse_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_upper_)
201
# reverse_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_lower_)
202
203
return LobattoLegendreMortarL2{RealT, nnodes_, typeof(forward_upper),
204
typeof(reverse_upper)}(forward_upper, forward_lower,
205
reverse_upper, reverse_lower)
206
end
207
208
function Base.show(io::IO, mortar::LobattoLegendreMortarL2)
209
@nospecialize mortar # reduce precompilation time
210
211
print(io, "LobattoLegendreMortarL2{", real(mortar), "}(polydeg=", polydeg(mortar),
212
")")
213
return nothing
214
end
215
function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarL2)
216
@nospecialize mortar # reduce precompilation time
217
218
print(io, "LobattoLegendreMortarL2{", real(mortar), "} with polynomials of degree ",
219
polydeg(mortar))
220
return nothing
221
end
222
223
@inline Base.real(mortar::LobattoLegendreMortarL2{RealT}) where {RealT} = RealT
224
225
@inline function nnodes(mortar::LobattoLegendreMortarL2{RealT, NNODES}) where {RealT,
226
NNODES}
227
return NNODES
228
end
229
230
@inline polydeg(mortar::LobattoLegendreMortarL2) = nnodes(mortar) - 1
231
232
# TODO: We can create EC mortars along the lines of the following implementation.
233
# abstract type AbstractMortarEC{RealT} <: AbstractMortar{RealT} end
234
235
# struct LobattoLegendreMortarEC{RealT<:Real, NNODES, MortarMatrix<:AbstractMatrix{RealT}, SurfaceFlux} <: AbstractMortarEC{RealT}
236
# forward_upper::MortarMatrix
237
# forward_lower::MortarMatrix
238
# reverse_upper::MortarMatrix
239
# reverse_lower::MortarMatrix
240
# surface_flux::SurfaceFlux
241
# end
242
243
# function MortarEC(basis::LobattoLegendreBasis{RealT}, surface_flux)
244
# forward_upper = calc_forward_upper(n_nodes, RealT)
245
# forward_lower = calc_forward_lower(n_nodes, RealT)
246
# l2reverse_upper = calc_reverse_upper(n_nodes, Val(:gauss_lobatto), RealT)
247
# l2reverse_lower = calc_reverse_lower(n_nodes, Val(:gauss_lobatto), RealT)
248
249
# # type conversions to make use of StaticArrays etc.
250
# nnodes_ = nnodes(basis)
251
# forward_upper = SMatrix{nnodes_, nnodes_}(forward_upper)
252
# forward_lower = SMatrix{nnodes_, nnodes_}(forward_lower)
253
# l2reverse_upper = SMatrix{nnodes_, nnodes_}(l2reverse_upper)
254
# l2reverse_lower = SMatrix{nnodes_, nnodes_}(l2reverse_lower)
255
256
# LobattoLegendreMortarEC{RealT, nnodes_, typeof(forward_upper), typeof(surface_flux)}(
257
# forward_upper, forward_lower,
258
# l2reverse_upper, l2reverse_lower,
259
# surface_flux
260
# )
261
# end
262
263
# @inline nnodes(mortar::LobattoLegendreMortarEC{RealT, NNODES}) = NNODES
264
265
struct LobattoLegendreAnalyzer{RealT <: Real, NNODES,
266
VectorT <: AbstractVector{RealT},
267
Vandermonde <: AbstractMatrix{RealT}} <:
268
SolutionAnalyzer{RealT}
269
nodes::VectorT
270
weights::VectorT
271
vandermonde::Vandermonde
272
end
273
274
function SolutionAnalyzer(basis::LobattoLegendreBasis;
275
analysis_polydeg = 2 * polydeg(basis))
276
RealT = real(basis)
277
nnodes_ = analysis_polydeg + 1
278
279
nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_, RealT)
280
vandermonde = polynomial_interpolation_matrix(get_nodes(basis), nodes_)
281
282
# Type conversions to enable possible optimizations of runtime performance
283
# and latency
284
nodes = SVector{nnodes_, RealT}(nodes_)
285
weights = SVector{nnodes_, RealT}(weights_)
286
287
return LobattoLegendreAnalyzer{RealT, nnodes_, typeof(nodes), typeof(vandermonde)}(nodes,
288
weights,
289
vandermonde)
290
end
291
292
function Base.show(io::IO, analyzer::LobattoLegendreAnalyzer)
293
@nospecialize analyzer # reduce precompilation time
294
295
print(io, "LobattoLegendreAnalyzer{", real(analyzer), "}(polydeg=",
296
polydeg(analyzer), ")")
297
return nothing
298
end
299
function Base.show(io::IO, ::MIME"text/plain", analyzer::LobattoLegendreAnalyzer)
300
@nospecialize analyzer # reduce precompilation time
301
302
print(io, "LobattoLegendreAnalyzer{", real(analyzer),
303
"} with polynomials of degree ", polydeg(analyzer))
304
return nothing
305
end
306
307
@inline Base.real(analyzer::LobattoLegendreAnalyzer{RealT}) where {RealT} = RealT
308
309
@inline function nnodes(analyzer::LobattoLegendreAnalyzer{RealT, NNODES}) where {RealT,
310
NNODES}
311
return NNODES
312
end
313
"""
314
eachnode(analyzer::LobattoLegendreAnalyzer)
315
316
Return an iterator over the indices that specify the location in relevant data structures
317
for the nodes in `analyzer`.
318
In particular, not the nodes themselves are returned.
319
"""
320
@inline eachnode(analyzer::LobattoLegendreAnalyzer) = Base.OneTo(nnodes(analyzer))
321
322
@inline polydeg(analyzer::LobattoLegendreAnalyzer) = nnodes(analyzer) - 1
323
324
struct LobattoLegendreAdaptorL2{RealT <: Real, NNODES,
325
ForwardMatrix <: AbstractMatrix{RealT},
326
ReverseMatrix <: AbstractMatrix{RealT}} <:
327
AdaptorL2{RealT}
328
forward_upper::ForwardMatrix
329
forward_lower::ForwardMatrix
330
reverse_upper::ReverseMatrix
331
reverse_lower::ReverseMatrix
332
end
333
334
function AdaptorL2(basis::LobattoLegendreBasis{RealT}) where {RealT}
335
nnodes_ = nnodes(basis)
336
337
forward_upper_ = calc_forward_upper(nnodes_, RealT)
338
forward_lower_ = calc_forward_lower(nnodes_, RealT)
339
reverse_upper_ = calc_reverse_upper(nnodes_, Val(:gauss), RealT)
340
reverse_lower_ = calc_reverse_lower(nnodes_, Val(:gauss), RealT)
341
342
# type conversions to get the requested real type and enable possible
343
# optimizations of runtime performance and latency
344
345
# TODO: Taal performance
346
# Check the performance of different implementations of
347
# `refine_elements!` (forward) and `coarsen_elements!` (reverse)
348
# with different types of the matrices.
349
# Check whether `@turbo` with `eachnode` in `multiply_dimensionwise!`
350
# can be faster than `@tullio` when the matrix sizes are not necessarily
351
# static.
352
forward_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(forward_upper_)
353
forward_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(forward_lower_)
354
# forward_upper = Matrix{RealT}(forward_upper_)
355
# forward_lower = Matrix{RealT}(forward_lower_)
356
357
reverse_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_upper_)
358
reverse_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_lower_)
359
# reverse_upper = Matrix{RealT}(reverse_upper_)
360
# reverse_lower = Matrix{RealT}(reverse_lower_)
361
362
return LobattoLegendreAdaptorL2{RealT, nnodes_, typeof(forward_upper),
363
typeof(reverse_upper)}(forward_upper, forward_lower,
364
reverse_upper, reverse_lower)
365
end
366
367
function Base.show(io::IO, adaptor::LobattoLegendreAdaptorL2)
368
@nospecialize adaptor # reduce precompilation time
369
370
print(io, "LobattoLegendreAdaptorL2{", real(adaptor), "}(polydeg=",
371
polydeg(adaptor), ")")
372
return nothing
373
end
374
function Base.show(io::IO, ::MIME"text/plain", adaptor::LobattoLegendreAdaptorL2)
375
@nospecialize adaptor # reduce precompilation time
376
377
print(io, "LobattoLegendreAdaptorL2{", real(adaptor),
378
"} with polynomials of degree ", polydeg(adaptor))
379
return nothing
380
end
381
382
@inline Base.real(adaptor::LobattoLegendreAdaptorL2{RealT}) where {RealT} = RealT
383
384
@inline function nnodes(adaptor::LobattoLegendreAdaptorL2{RealT, NNODES}) where {RealT,
385
NNODES}
386
return NNODES
387
end
388
389
@inline polydeg(adaptor::LobattoLegendreAdaptorL2) = nnodes(adaptor) - 1
390
391
###############################################################################
392
# Polynomial derivative and interpolation functions
393
394
# TODO: Taal refactor, allow other RealT below and adapt constructors above accordingly
395
396
# Calculate the Dhat matrix = -M^{-1} D^T M for weak form differentiation.
397
# Note that this is the negated version of the matrix that shows up on the RHS of the
398
# DG update multiplying the physical flux evaluations.
399
function calc_Dhat(derivative_matrix, weights)
400
n_nodes = length(weights)
401
Dhat = Matrix(derivative_matrix') # D^T
402
403
# Perform M matrix multiplications and negate
404
for n in 1:n_nodes, j in 1:n_nodes
405
Dhat[j, n] *= -weights[n] / weights[j]
406
end
407
408
return Dhat
409
end
410
411
# Calculate the Dsplit matrix for split-form differentiation: Dsplit = 2D - M⁻¹B
412
# Note that this is the negated version of the matrix that shows up on the RHS of the
413
# DG update multiplying the two-point numerical volume flux evaluations.
414
function calc_Dsplit(derivative_matrix, weights)
415
# Start with 2 x the normal D matrix
416
Dsplit = 2 .* derivative_matrix
417
418
# Modify to account for the weighted boundary terms
419
Dsplit[1, 1] += 1 / weights[1] # B[1, 1] = -1
420
Dsplit[end, end] -= 1 / weights[end] # B[end, end] = 1
421
422
return Dsplit
423
end
424
425
# Calculate the polynomial derivative matrix D.
426
# This implements algorithm 37 "PolynomialDerivativeMatrix" from Kopriva's book.
427
function polynomial_derivative_matrix(nodes)
428
n_nodes = length(nodes)
429
D = zeros(eltype(nodes), n_nodes, n_nodes)
430
wbary = barycentric_weights(nodes)
431
432
for i in 1:n_nodes, j in 1:n_nodes
433
if j != i
434
D[i, j] = (wbary[j] / wbary[i]) * 1 / (nodes[i] - nodes[j])
435
D[i, i] -= D[i, j]
436
end
437
end
438
439
return D
440
end
441
442
# Calculate and interpolation matrix (Vandermonde matrix) between two given sets of nodes
443
# See algorithm 32 "PolynomialInterpolationMatrix" from Kopriva's book.
444
function polynomial_interpolation_matrix(nodes_in, nodes_out,
445
baryweights_in = barycentric_weights(nodes_in))
446
n_nodes_in = length(nodes_in)
447
n_nodes_out = length(nodes_out)
448
vandermonde = Matrix{promote_type(eltype(nodes_in), eltype(nodes_out))}(undef,
449
n_nodes_out,
450
n_nodes_in)
451
polynomial_interpolation_matrix!(vandermonde, nodes_in, nodes_out, baryweights_in)
452
453
return vandermonde
454
end
455
456
# This implements algorithm 32 "PolynomialInterpolationMatrix" from Kopriva's book.
457
function polynomial_interpolation_matrix!(vandermonde,
458
nodes_in, nodes_out,
459
baryweights_in)
460
fill!(vandermonde, zero(eltype(vandermonde)))
461
462
for k in eachindex(nodes_out)
463
match = false
464
for j in eachindex(nodes_in)
465
if isapprox(nodes_out[k], nodes_in[j])
466
match = true
467
vandermonde[k, j] = 1
468
end
469
end
470
471
if match == false
472
s = zero(eltype(vandermonde))
473
for j in eachindex(nodes_in)
474
t = baryweights_in[j] / (nodes_out[k] - nodes_in[j])
475
vandermonde[k, j] = t
476
s += t
477
end
478
for j in eachindex(nodes_in)
479
vandermonde[k, j] = vandermonde[k, j] / s
480
end
481
end
482
end
483
484
return vandermonde
485
end
486
487
"""
488
barycentric_weights(nodes)
489
490
Calculate the barycentric weights for a given node distribution, i.e.,
491
```math
492
w_j = \\frac{1}{ \\prod_{k \\neq j} \\left( x_j - x_k \\right ) }
493
```
494
495
For details, see (especially Section 3)
496
- Jean-Paul Berrut and Lloyd N. Trefethen (2004).
497
Barycentric Lagrange Interpolation.
498
[DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715)
499
"""
500
function barycentric_weights(nodes)
501
n_nodes = length(nodes)
502
weights = ones(eltype(nodes), n_nodes)
503
504
for j in 2:n_nodes, k in 1:(j - 1)
505
weights[k] *= nodes[k] - nodes[j]
506
weights[j] *= nodes[j] - nodes[k]
507
end
508
509
for j in 1:n_nodes
510
weights[j] = 1 / weights[j]
511
end
512
513
return weights
514
end
515
516
"""
517
lagrange_interpolating_polynomials(x, nodes, wbary)
518
519
Calculate Lagrange polynomials for a given node distribution with
520
associated barycentric weights `wbary` at a given point `x` on the
521
reference interval ``[-1, 1]``.
522
523
This returns all ``l_j(x)``, i.e., the Lagrange polynomials for each node ``x_j``.
524
Thus, to obtain the interpolating polynomial ``p(x)`` at ``x``, one has to
525
multiply the Lagrange polynomials with the nodal values ``u_j`` and sum them up:
526
``p(x) = \\sum_{j=1}^{n} u_j l_j(x)``.
527
528
For details, see e.g. Section 2 of
529
- Jean-Paul Berrut and Lloyd N. Trefethen (2004).
530
Barycentric Lagrange Interpolation.
531
[DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715)
532
"""
533
function lagrange_interpolating_polynomials(x, nodes, wbary)
534
n_nodes = length(nodes)
535
polynomials = zeros(eltype(nodes), n_nodes)
536
537
for i in 1:n_nodes
538
# Avoid division by zero when `x` is close to node by using
539
# the Kronecker-delta property at nodes
540
# of the Lagrange interpolation polynomials.
541
if isapprox(x, nodes[i], rtol = eps(x))
542
polynomials[i] = 1
543
return polynomials
544
end
545
end
546
547
for i in 1:n_nodes
548
polynomials[i] = wbary[i] / (x - nodes[i])
549
end
550
total = sum(polynomials)
551
552
for i in 1:n_nodes
553
polynomials[i] /= total
554
end
555
556
return polynomials
557
end
558
559
"""
560
gauss_lobatto_nodes_weights(n_nodes::Integer, RealT = Float64)
561
562
Computes nodes ``x_j`` and weights ``w_j`` for the (Legendre-)Gauss-Lobatto quadrature.
563
This implements algorithm 25 "GaussLobattoNodesAndWeights" from the book
564
565
- David A. Kopriva, (2009).
566
Implementing spectral methods for partial differential equations:
567
Algorithms for scientists and engineers.
568
[DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5)
569
"""
570
function gauss_lobatto_nodes_weights(n_nodes::Integer, RealT = Float64)
571
# From FLUXO (but really from blue book by Kopriva)
572
n_iterations = 20
573
tolerance = 2 * eps(RealT) # Relative tolerance for Newton iteration
574
575
# Initialize output
576
nodes = zeros(RealT, n_nodes)
577
weights = zeros(RealT, n_nodes)
578
579
# Special case for polynomial degree zero (first order finite volume):
580
# Fall back to Gauss-Legendre
581
if n_nodes == 1
582
nodes[1] = 0
583
weights[1] = 2
584
return nodes, weights
585
end
586
587
# Get polynomial degree for convenience
588
N = n_nodes - 1
589
590
# Calculate values at boundary
591
nodes[1] = -1
592
nodes[end] = 1
593
weights[1] = RealT(2) / (N * (N + 1))
594
weights[end] = weights[1]
595
596
# Calculate interior values
597
if N > 1
598
cont1 = convert(RealT, pi) / N
599
cont2 = 3 / (8 * N * convert(RealT, pi))
600
601
# Use symmetry -> only left side is computed
602
for i in 1:(div(N + 1, 2) - 1)
603
# Calculate node
604
# Initial guess for Newton method
605
nodes[i + 1] = -cos(cont1 * (i + 0.25) - cont2 / (i + 0.25))
606
607
# Newton iteration to find root of Legendre polynomial (= integration node)
608
for k in 0:n_iterations
609
q, qder, _ = calc_q_and_l(N, nodes[i + 1])
610
dx = -q / qder
611
nodes[i + 1] += dx
612
if abs(dx) < tolerance * abs(nodes[i + 1])
613
break
614
end
615
616
if k == n_iterations
617
@warn "`gauss_lobatto_nodes_weights` Newton iteration did not converge"
618
end
619
end
620
621
# Calculate weight
622
_, _, L = calc_q_and_l(N, nodes[i + 1])
623
weights[i + 1] = weights[1] / L^2
624
625
# Set nodes and weights according to symmetry properties
626
nodes[N + 1 - i] = -nodes[i + 1]
627
weights[N + 1 - i] = weights[i + 1]
628
end
629
end
630
631
# If odd number of nodes, set center node to origin (= 0.0) and calculate weight
632
if n_nodes % 2 == 1
633
_, _, L = calc_q_and_l(N, zero(RealT))
634
nodes[div(N, 2) + 1] = 0
635
weights[div(N, 2) + 1] = weights[1] / L^2
636
end
637
638
return nodes, weights
639
end
640
641
# From FLUXO (but really from blue book by Kopriva, algorithm 24)
642
function calc_q_and_l(N::Integer, x::Real)
643
RealT = typeof(x)
644
645
L_Nm2 = one(RealT)
646
L_Nm1 = x
647
Lder_Nm2 = zero(RealT)
648
Lder_Nm1 = one(RealT)
649
650
local L
651
for i in 2:N
652
L = ((2 * i - 1) * x * L_Nm1 - (i - 1) * L_Nm2) / i
653
Lder = Lder_Nm2 + (2 * i - 1) * L_Nm1
654
L_Nm2 = L_Nm1
655
L_Nm1 = L
656
Lder_Nm2 = Lder_Nm1
657
Lder_Nm1 = Lder
658
end
659
660
q = (2 * N + 1) / (N + 1) * (x * L - L_Nm2)
661
qder = (2 * N + 1) * L
662
663
return q, qder, L
664
end
665
666
"""
667
legendre_polynomial_and_derivative(N::Int, x::Real)
668
669
Computes the Legendre polynomial of degree `N` and its derivative at `x`.
670
This implements algorithm 22 "LegendrePolynomialAndDerivative" from the book
671
672
- David A. Kopriva, (2009).
673
Implementing spectral methods for partial differential equations:
674
Algorithms for scientists and engineers.
675
[DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5)
676
"""
677
function legendre_polynomial_and_derivative(N::Int, x::Real)
678
RealT = typeof(x)
679
if N == 0
680
poly = one(RealT)
681
deriv = zero(RealT)
682
elseif N == 1
683
poly = x
684
deriv = one(RealT)
685
else
686
poly_Nm2 = one(RealT)
687
poly_Nm1 = copy(x)
688
deriv_Nm2 = zero(RealT)
689
deriv_Nm1 = one(RealT)
690
691
poly = zero(RealT)
692
deriv = zero(RealT)
693
for i in 2:N
694
poly = ((2 * i - 1) * x * poly_Nm1 - (i - 1) * poly_Nm2) / i
695
deriv = deriv_Nm2 + (2 * i - 1) * poly_Nm1
696
poly_Nm2 = poly_Nm1
697
poly_Nm1 = poly
698
deriv_Nm2 = deriv_Nm1
699
deriv_Nm1 = deriv
700
end
701
end
702
703
# Normalize
704
poly = poly * sqrt(N + 0.5)
705
deriv = deriv * sqrt(N + 0.5)
706
707
return poly, deriv
708
end
709
710
# Calculate Legendre vandermonde matrix and its inverse
711
function vandermonde_legendre(nodes, N::Integer, RealT = Float64)
712
n_nodes = length(nodes)
713
n_modes = N + 1
714
vandermonde = zeros(RealT, n_nodes, n_modes)
715
716
for i in 1:n_nodes
717
for m in 1:n_modes
718
vandermonde[i, m], _ = legendre_polynomial_and_derivative(m - 1, nodes[i])
719
end
720
end
721
# for very high polynomial degree, this is not well conditioned
722
inverse_vandermonde = inv(vandermonde)
723
return vandermonde, inverse_vandermonde
724
end
725
vandermonde_legendre(nodes, RealT = Float64) = vandermonde_legendre(nodes,
726
length(nodes) - 1,
727
RealT)
728
end # @muladd
729
730