Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem/basis_gauss_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
GaussLegendreBasis([RealT=Float64,] polydeg::Integer)
10
11
Create a nodal Gauss-Legendre basis for polynomials of degree `polydeg`.
12
"""
13
struct GaussLegendreBasis{RealT <: Real, NNODES,
14
VectorT <: AbstractVector{RealT},
15
InverseVandermondeLegendre <: AbstractMatrix{RealT},
16
DerivativeMatrix <: AbstractMatrix{RealT},
17
BoundaryMatrix <: AbstractMatrix{RealT}} <:
18
AbstractBasisSBP{RealT}
19
nodes::VectorT
20
weights::VectorT
21
inverse_weights::VectorT
22
23
inverse_vandermonde_legendre::InverseVandermondeLegendre
24
25
derivative_matrix::DerivativeMatrix # strong form derivative matrix
26
# `derivative_split` currently not implemented since
27
# Flux-Differencing is not supported for Gauss-Legendre DGSEM at the moment.
28
derivative_hat::DerivativeMatrix # weak form matrix "dhat", negative adjoint wrt the SBP dot product
29
30
# Required for Gauss-Legendre nodes (non-trivial interpolation to the boundaries)
31
boundary_interpolation::BoundaryMatrix # L
32
boundary_interpolation_inverse_weights::BoundaryMatrix # M^{-1} * L = Lhat
33
end
34
35
function GaussLegendreBasis(RealT, polydeg::Integer)
36
nnodes_ = polydeg + 1
37
38
nodes_, weights_ = gauss_nodes_weights(nnodes_, RealT)
39
inverse_weights_ = inv.(weights_)
40
41
_, inverse_vandermonde_legendre = vandermonde_legendre(nodes_, RealT)
42
43
derivative_matrix = polynomial_derivative_matrix(nodes_)
44
derivative_hat = calc_Dhat(derivative_matrix, weights_)
45
46
# Type conversions to enable possible optimizations of runtime performance
47
# and latency
48
nodes = SVector{nnodes_, RealT}(nodes_)
49
weights = SVector{nnodes_, RealT}(weights_)
50
inverse_weights = SVector{nnodes_, RealT}(inverse_weights_)
51
52
boundary_interpolation = zeros(RealT, nnodes_, 2)
53
boundary_interpolation[:, 1] = calc_L(-one(RealT), nodes_, weights_)
54
boundary_interpolation[:, 2] = calc_L(one(RealT), nodes_, weights_)
55
56
boundary_interpolation_inverse_weights = copy(boundary_interpolation)
57
boundary_interpolation_inverse_weights[:, 1] = calc_Lhat(boundary_interpolation[:,
58
1],
59
weights_)
60
boundary_interpolation_inverse_weights[:, 2] = calc_Lhat(boundary_interpolation[:,
61
2],
62
weights_)
63
64
# We keep the matrices above stored using the standard `Matrix` type
65
# since this is usually as fast as `SMatrix`
66
# (when using `let` in the volume integral/`@threaded`)
67
# and reduces latency
68
69
return GaussLegendreBasis{RealT, nnodes_, typeof(nodes),
70
typeof(inverse_vandermonde_legendre),
71
typeof(derivative_matrix),
72
typeof(boundary_interpolation)}(nodes, weights,
73
inverse_weights,
74
inverse_vandermonde_legendre,
75
derivative_matrix,
76
derivative_hat,
77
boundary_interpolation,
78
boundary_interpolation_inverse_weights)
79
end
80
81
GaussLegendreBasis(polydeg::Integer) = GaussLegendreBasis(Float64, polydeg)
82
83
function Base.show(io::IO, basis::GaussLegendreBasis)
84
@nospecialize basis # reduce precompilation time
85
86
print(io, "GaussLegendreBasis{", real(basis), "}(polydeg=", polydeg(basis), ")")
87
return nothing
88
end
89
90
function Base.show(io::IO, ::MIME"text/plain", basis::GaussLegendreBasis)
91
@nospecialize basis # reduce precompilation time
92
93
print(io, "GaussLegendreBasis{", real(basis), "} with polynomials of degree ",
94
polydeg(basis))
95
return nothing
96
end
97
98
@inline Base.real(basis::GaussLegendreBasis{RealT}) where {RealT} = RealT
99
100
@inline function nnodes(basis::GaussLegendreBasis{RealT, NNODES}) where {RealT, NNODES}
101
return NNODES
102
end
103
104
"""
105
eachnode(basis::GaussLegendreBasis)
106
107
Return an iterator over the indices that specify the location in relevant data structures
108
for the nodes in `basis`.
109
In particular, not the nodes themselves are returned.
110
"""
111
@inline eachnode(basis::GaussLegendreBasis) = Base.OneTo(nnodes(basis))
112
113
@inline polydeg(basis::GaussLegendreBasis) = nnodes(basis) - 1
114
115
@inline get_nodes(basis::GaussLegendreBasis) = basis.nodes
116
117
"""
118
integrate(f, u, basis::GaussLegendreBasis)
119
120
Map the function `f` to the coefficients `u` and integrate with respect to the
121
quadrature rule given by `basis`.
122
"""
123
function integrate(f, u, basis::GaussLegendreBasis)
124
@unpack weights = basis
125
126
res = zero(f(first(u)))
127
for i in eachindex(u, weights)
128
res += f(u[i]) * weights[i]
129
end
130
return res
131
end
132
133
# TODO: Not yet implemented
134
function MortarL2(basis::GaussLegendreBasis)
135
return nothing
136
end
137
138
"""
139
gauss_nodes_weights(n_nodes::Integer, RealT = Float64)
140
141
Computes nodes ``x_j`` and weights ``w_j`` for the Gauss-Legendre quadrature.
142
This implements algorithm 23 "LegendreGaussNodesAndWeights" from the book
143
144
- David A. Kopriva, (2009).
145
Implementing spectral methods for partial differential equations:
146
Algorithms for scientists and engineers.
147
[DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5)
148
"""
149
function gauss_nodes_weights(n_nodes::Integer, RealT = Float64)
150
n_iterations = 20
151
tolerance = 2 * eps(RealT) # Relative tolerance for Newton iteration
152
153
# Initialize output
154
nodes = ones(RealT, n_nodes)
155
weights = zeros(RealT, n_nodes)
156
157
# Get polynomial degree for convenience
158
N = n_nodes - 1
159
if N == 0
160
nodes .= 0
161
weights .= 2
162
return nodes, weights
163
elseif N == 1
164
nodes[1] = -sqrt(one(RealT) / 3)
165
nodes[end] = -nodes[1]
166
weights .= 1
167
return nodes, weights
168
else # N > 1
169
# Use symmetry property of the roots of the Legendre polynomials
170
for i in 0:(div(N + 1, 2) - 1)
171
# Starting guess for Newton method
172
nodes[i + 1] = -cospi(one(RealT) / (2 * N + 2) * (2 * i + 1))
173
174
# Newton iteration to find root of Legendre polynomial (= integration node)
175
for k in 0:n_iterations
176
poly, deriv = legendre_polynomial_and_derivative(N + 1, nodes[i + 1])
177
dx = -poly / deriv
178
nodes[i + 1] += dx
179
if abs(dx) < tolerance * abs(nodes[i + 1])
180
break
181
end
182
183
if k == n_iterations
184
@warn "`gauss_nodes_weights` Newton iteration did not converge"
185
end
186
end
187
188
# Calculate weight
189
poly, deriv = legendre_polynomial_and_derivative(N + 1, nodes[i + 1])
190
weights[i + 1] = (2 * N + 3) / ((1 - nodes[i + 1]^2) * deriv^2)
191
192
# Set nodes and weights according to symmetry properties
193
nodes[N + 1 - i] = -nodes[i + 1]
194
weights[N + 1 - i] = weights[i + 1]
195
end
196
197
# If odd number of nodes, set center node to origin (= 0.0) and calculate weight
198
if n_nodes % 2 == 1
199
poly, deriv = legendre_polynomial_and_derivative(N + 1, zero(RealT))
200
nodes[div(N, 2) + 1] = 0
201
weights[div(N, 2) + 1] = (2 * N + 3) / deriv^2
202
end
203
204
return nodes, weights
205
end
206
end
207
208
# L(x), where L(x) is the Lagrange polynomial vector at point x.
209
function calc_L(x, nodes, weights)
210
n_nodes = length(nodes)
211
wbary = barycentric_weights(nodes)
212
213
return lagrange_interpolating_polynomials(x, nodes, wbary)
214
end
215
216
# Calculate M^{-1} * L(x), where L(x) is the Lagrange polynomial
217
# vector at point x.
218
# Not required for the DGSEM with LGL basis, as boundary evaluations
219
# collapse to boundary node evaluations.
220
function calc_Lhat(L, weights)
221
Lhat = copy(L)
222
for i in 1:length(weights)
223
Lhat[i] /= weights[i]
224
end
225
226
return Lhat
227
end
228
229
struct GaussLegendreAnalyzer{RealT <: Real, NNODES,
230
VectorT <: AbstractVector{RealT},
231
Vandermonde <: AbstractMatrix{RealT}} <:
232
SolutionAnalyzer{RealT}
233
nodes::VectorT
234
weights::VectorT
235
vandermonde::Vandermonde
236
end
237
238
function SolutionAnalyzer(basis::GaussLegendreBasis;
239
analysis_polydeg = 2 * polydeg(basis))
240
RealT = real(basis)
241
nnodes_ = analysis_polydeg + 1
242
243
# compute everything using `Float64` by default
244
nodes_, weights_ = gauss_nodes_weights(nnodes_)
245
vandermonde_ = polynomial_interpolation_matrix(get_nodes(basis), nodes_)
246
247
# type conversions to get the requested real type and enable possible
248
# optimizations of runtime performance and latency
249
nodes = SVector{nnodes_, RealT}(nodes_)
250
weights = SVector{nnodes_, RealT}(weights_)
251
252
vandermonde = Matrix{RealT}(vandermonde_)
253
254
return GaussLegendreAnalyzer{RealT, nnodes_, typeof(nodes), typeof(vandermonde)}(nodes,
255
weights,
256
vandermonde)
257
end
258
259
function Base.show(io::IO, analyzer::GaussLegendreAnalyzer)
260
@nospecialize analyzer # reduce precompilation time
261
262
print(io, "GaussLegendreAnalyzer{", real(analyzer), "}(polydeg=",
263
polydeg(analyzer), ")")
264
return nothing
265
end
266
267
function Base.show(io::IO, ::MIME"text/plain", analyzer::GaussLegendreAnalyzer)
268
@nospecialize analyzer # reduce precompilation time
269
270
print(io, "GaussLegendreAnalyzer{", real(analyzer),
271
"} with polynomials of degree ", polydeg(analyzer))
272
return nothing
273
end
274
275
@inline Base.real(analyzer::GaussLegendreAnalyzer{RealT}) where {RealT} = RealT
276
277
@inline function nnodes(analyzer::GaussLegendreAnalyzer{RealT, NNODES}) where {RealT,
278
NNODES}
279
return NNODES
280
end
281
282
"""
283
eachnode(analyzer::GaussLegendreAnalyzer)
284
285
Return an iterator over the indices that specify the location in relevant data structures
286
for the nodes in `analyzer`.
287
In particular, not the nodes themselves are returned.
288
"""
289
@inline eachnode(analyzer::GaussLegendreAnalyzer) = Base.OneTo(nnodes(analyzer))
290
291
@inline polydeg(analyzer::GaussLegendreAnalyzer) = nnodes(analyzer) - 1
292
end # @muladd
293
294