Path: blob/main/src/solvers/dgsem/basis_gauss_legendre.jl
5591 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67"""8GaussLegendreBasis([RealT=Float64,] polydeg::Integer)910Create a nodal Gauss-Legendre basis for polynomials of degree `polydeg`.11"""12struct GaussLegendreBasis{RealT <: Real, NNODES,13VectorT <: AbstractVector{RealT},14InverseVandermondeLegendre <: AbstractMatrix{RealT},15DerivativeMatrix <: AbstractMatrix{RealT},16BoundaryMatrix <: AbstractMatrix{RealT}} <:17AbstractBasisSBP{RealT}18nodes::VectorT19weights::VectorT20inverse_weights::VectorT2122inverse_vandermonde_legendre::InverseVandermondeLegendre2324derivative_matrix::DerivativeMatrix # strong form derivative matrix25# `derivative_split` currently not implemented since26# Flux-Differencing is not supported for Gauss-Legendre DGSEM at the moment.27derivative_hat::DerivativeMatrix # weak form matrix "dhat", negative adjoint wrt the SBP dot product2829# Required for Gauss-Legendre nodes (non-trivial interpolation to the boundaries)30boundary_interpolation::BoundaryMatrix # L31boundary_interpolation_inverse_weights::BoundaryMatrix # M^{-1} * L = Lhat32end3334function GaussLegendreBasis(RealT, polydeg::Integer)35nnodes_ = polydeg + 13637nodes_, weights_ = gauss_nodes_weights(nnodes_, RealT)38inverse_weights_ = inv.(weights_)3940_, inverse_vandermonde_legendre = vandermonde_legendre(nodes_, RealT)4142derivative_matrix = polynomial_derivative_matrix(nodes_)43derivative_hat = calc_Dhat(derivative_matrix, weights_)4445# Type conversions to enable possible optimizations of runtime performance46# and latency47nodes = SVector{nnodes_, RealT}(nodes_)48weights = SVector{nnodes_, RealT}(weights_)49inverse_weights = SVector{nnodes_, RealT}(inverse_weights_)5051boundary_interpolation = zeros(RealT, nnodes_, 2)52boundary_interpolation[:, 1] = calc_L(-one(RealT), nodes_, weights_)53boundary_interpolation[:, 2] = calc_L(one(RealT), nodes_, weights_)5455boundary_interpolation_inverse_weights = copy(boundary_interpolation)56boundary_interpolation_inverse_weights[:, 1] = calc_Lhat(boundary_interpolation[:,571],58weights_)59boundary_interpolation_inverse_weights[:, 2] = calc_Lhat(boundary_interpolation[:,602],61weights_)6263# We keep the matrices above stored using the standard `Matrix` type64# since this is usually as fast as `SMatrix`65# (when using `let` in the volume integral/`@threaded`)66# and reduces latency6768return GaussLegendreBasis{RealT, nnodes_, typeof(nodes),69typeof(inverse_vandermonde_legendre),70typeof(derivative_matrix),71typeof(boundary_interpolation)}(nodes, weights,72inverse_weights,73inverse_vandermonde_legendre,74derivative_matrix,75derivative_hat,76boundary_interpolation,77boundary_interpolation_inverse_weights)78end7980GaussLegendreBasis(polydeg::Integer) = GaussLegendreBasis(Float64, polydeg)8182function Base.show(io::IO, basis::GaussLegendreBasis)83@nospecialize basis # reduce precompilation time8485print(io, "GaussLegendreBasis{", real(basis), "}(polydeg=", polydeg(basis), ")")86return nothing87end8889function Base.show(io::IO, ::MIME"text/plain", basis::GaussLegendreBasis)90@nospecialize basis # reduce precompilation time9192print(io, "GaussLegendreBasis{", real(basis), "} with polynomials of degree ",93polydeg(basis))94return nothing95end9697@inline Base.real(basis::GaussLegendreBasis{RealT}) where {RealT} = RealT9899@inline function nnodes(basis::GaussLegendreBasis{RealT, NNODES}) where {RealT, NNODES}100return NNODES101end102103"""104eachnode(basis::GaussLegendreBasis)105106Return an iterator over the indices that specify the location in relevant data structures107for the nodes in `basis`.108In particular, not the nodes themselves are returned.109"""110@inline eachnode(basis::GaussLegendreBasis) = Base.OneTo(nnodes(basis))111112@inline polydeg(basis::GaussLegendreBasis) = nnodes(basis) - 1113114@inline get_nodes(basis::GaussLegendreBasis) = basis.nodes115116"""117integrate(f, u, basis::GaussLegendreBasis)118119Map the function `f` to the coefficients `u` and integrate with respect to the120quadrature rule given by `basis`.121"""122function integrate(f, u, basis::GaussLegendreBasis)123@unpack weights = basis124125res = zero(f(first(u)))126for i in eachindex(u, weights)127res += f(u[i]) * weights[i]128end129return res130end131132# TODO: Not yet implemented133function MortarL2(basis::GaussLegendreBasis)134return nothing135end136137"""138gauss_nodes_weights(n_nodes::Integer, RealT = Float64)139140Computes nodes ``x_j`` and weights ``w_j`` for the Gauss-Legendre quadrature.141This implements algorithm 23 "LegendreGaussNodesAndWeights" from the book142143- David A. Kopriva, (2009).144Implementing spectral methods for partial differential equations:145Algorithms for scientists and engineers.146[DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5)147"""148function gauss_nodes_weights(n_nodes::Integer, RealT = Float64)149n_iterations = 20150tolerance = 2 * eps(RealT) # Relative tolerance for Newton iteration151152# Initialize output153nodes = ones(RealT, n_nodes)154weights = zeros(RealT, n_nodes)155156# Get polynomial degree for convenience157N = n_nodes - 1158if N == 0159nodes .= 0160weights .= 2161return nodes, weights162elseif N == 1163nodes[1] = -sqrt(one(RealT) / 3)164nodes[end] = -nodes[1]165weights .= 1166return nodes, weights167else # N > 1168# Use symmetry property of the roots of the Legendre polynomials169for i in 0:(div(N + 1, 2) - 1)170# Starting guess for Newton method171nodes[i + 1] = -cospi(one(RealT) / (2 * N + 2) * (2 * i + 1))172173# Newton iteration to find root of Legendre polynomial (= integration node)174for k in 0:n_iterations175poly, deriv = legendre_polynomial_and_derivative(N + 1, nodes[i + 1])176dx = -poly / deriv177nodes[i + 1] += dx178if abs(dx) < tolerance * abs(nodes[i + 1])179break180end181182if k == n_iterations183@warn "`gauss_nodes_weights` Newton iteration did not converge"184end185end186187# Calculate weight188poly, deriv = legendre_polynomial_and_derivative(N + 1, nodes[i + 1])189weights[i + 1] = (2 * N + 3) / ((1 - nodes[i + 1]^2) * deriv^2)190191# Set nodes and weights according to symmetry properties192nodes[N + 1 - i] = -nodes[i + 1]193weights[N + 1 - i] = weights[i + 1]194end195196# If odd number of nodes, set center node to origin (= 0.0) and calculate weight197if n_nodes % 2 == 1198poly, deriv = legendre_polynomial_and_derivative(N + 1, zero(RealT))199nodes[div(N, 2) + 1] = 0200weights[div(N, 2) + 1] = (2 * N + 3) / deriv^2201end202203return nodes, weights204end205end206207# L(x), where L(x) is the Lagrange polynomial vector at point x.208function calc_L(x, nodes, weights)209n_nodes = length(nodes)210wbary = barycentric_weights(nodes)211212return lagrange_interpolating_polynomials(x, nodes, wbary)213end214215# Calculate M^{-1} * L(x), where L(x) is the Lagrange polynomial216# vector at point x.217# Not required for the DGSEM with LGL basis, as boundary evaluations218# collapse to boundary node evaluations.219function calc_Lhat(L, weights)220Lhat = copy(L)221for i in 1:length(weights)222Lhat[i] /= weights[i]223end224225return Lhat226end227228struct GaussLegendreAnalyzer{RealT <: Real, NNODES,229VectorT <: AbstractVector{RealT},230Vandermonde <: AbstractMatrix{RealT}} <:231SolutionAnalyzer{RealT}232nodes::VectorT233weights::VectorT234vandermonde::Vandermonde235end236237function SolutionAnalyzer(basis::GaussLegendreBasis;238analysis_polydeg = 2 * polydeg(basis))239RealT = real(basis)240nnodes_ = analysis_polydeg + 1241242# compute everything using `Float64` by default243nodes_, weights_ = gauss_nodes_weights(nnodes_)244vandermonde_ = polynomial_interpolation_matrix(get_nodes(basis), nodes_)245246# type conversions to get the requested real type and enable possible247# optimizations of runtime performance and latency248nodes = SVector{nnodes_, RealT}(nodes_)249weights = SVector{nnodes_, RealT}(weights_)250251vandermonde = Matrix{RealT}(vandermonde_)252253return GaussLegendreAnalyzer{RealT, nnodes_, typeof(nodes), typeof(vandermonde)}(nodes,254weights,255vandermonde)256end257258function Base.show(io::IO, analyzer::GaussLegendreAnalyzer)259@nospecialize analyzer # reduce precompilation time260261print(io, "GaussLegendreAnalyzer{", real(analyzer), "}(polydeg=",262polydeg(analyzer), ")")263return nothing264end265266function Base.show(io::IO, ::MIME"text/plain", analyzer::GaussLegendreAnalyzer)267@nospecialize analyzer # reduce precompilation time268269print(io, "GaussLegendreAnalyzer{", real(analyzer),270"} with polynomials of degree ", polydeg(analyzer))271return nothing272end273274@inline Base.real(analyzer::GaussLegendreAnalyzer{RealT}) where {RealT} = RealT275276@inline function nnodes(analyzer::GaussLegendreAnalyzer{RealT, NNODES}) where {RealT,277NNODES}278return NNODES279end280281"""282eachnode(analyzer::GaussLegendreAnalyzer)283284Return an iterator over the indices that specify the location in relevant data structures285for the nodes in `analyzer`.286In particular, not the nodes themselves are returned.287"""288@inline eachnode(analyzer::GaussLegendreAnalyzer) = Base.OneTo(nnodes(analyzer))289290@inline polydeg(analyzer::GaussLegendreAnalyzer) = nnodes(analyzer) - 1291end # @muladd292293294