Path: blob/main/src/solvers/dgsem/basis_lobatto_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"""8LobattoLegendreBasis([RealT = Float64,] polydeg::Integer)910Create a nodal Lobatto-Legendre basis for polynomials of degree `polydeg`.1112For the special case `polydeg=0` the DG method reduces to a finite volume method.13Therefore, this function sets the center point of the cell as single node.14This exceptional case is currently only supported for TreeMesh!15"""16struct LobattoLegendreBasis{RealT <: Real, NNODES,17VectorT <: AbstractVector{RealT},18InverseVandermondeLegendre <: AbstractMatrix{RealT},19DerivativeMatrix <: AbstractMatrix{RealT}} <:20AbstractBasisSBP{RealT}21nodes::VectorT22weights::VectorT23inverse_weights::VectorT2425inverse_vandermonde_legendre::InverseVandermondeLegendre2627derivative_matrix::DerivativeMatrix # strong form derivative matrix "D"28derivative_split::DerivativeMatrix # strong form derivative matrix minus boundary terms29derivative_hat::DerivativeMatrix # weak form matrix "Dhat", negative adjoint wrt the SBP dot product30end3132function Adapt.adapt_structure(to, basis::LobattoLegendreBasis)33inverse_vandermonde_legendre = adapt(to, basis.inverse_vandermonde_legendre)34RealT = eltype(inverse_vandermonde_legendre)3536nodes = SVector{<:Any, RealT}(basis.nodes)37weights = SVector{<:Any, RealT}(basis.weights)38inverse_weights = SVector{<:Any, RealT}(basis.inverse_weights)39derivative_matrix = adapt(to, basis.derivative_matrix)40derivative_split = adapt(to, basis.derivative_split)41derivative_hat = adapt(to, basis.derivative_hat)42return LobattoLegendreBasis{RealT, nnodes(basis), typeof(nodes),43typeof(inverse_vandermonde_legendre),44typeof(derivative_matrix)}(nodes,45weights,46inverse_weights,47inverse_vandermonde_legendre,48derivative_matrix,49derivative_split,50derivative_hat)51end5253function LobattoLegendreBasis(RealT, polydeg::Integer)54nnodes_ = polydeg + 15556nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_, RealT)57inverse_weights_ = inv.(weights_)5859_, inverse_vandermonde_legendre = vandermonde_legendre(nodes_, RealT)6061derivative_matrix = polynomial_derivative_matrix(nodes_)62derivative_split = calc_Dsplit(derivative_matrix, weights_)63derivative_hat = calc_Dhat(derivative_matrix, weights_)6465# Type conversions to enable possible optimizations of runtime performance66# and latency67nodes = SVector{nnodes_, RealT}(nodes_)68weights = SVector{nnodes_, RealT}(weights_)69inverse_weights = SVector{nnodes_, RealT}(inverse_weights_)7071# We keep the matrices above stored using the standard `Matrix` type72# since this is usually as fast as `SMatrix`73# (when using `let` in the volume integral/`@threaded`)74# and reduces latency7576return LobattoLegendreBasis{RealT, nnodes_, typeof(nodes),77typeof(inverse_vandermonde_legendre),78typeof(derivative_matrix)}(nodes, weights,79inverse_weights,80inverse_vandermonde_legendre,81derivative_matrix,82derivative_split,83derivative_hat)84end85LobattoLegendreBasis(polydeg::Integer) = LobattoLegendreBasis(Float64, polydeg)8687function Base.show(io::IO, basis::LobattoLegendreBasis)88@nospecialize basis # reduce precompilation time8990print(io, "LobattoLegendreBasis{", real(basis), "}(polydeg=", polydeg(basis), ")")91return nothing92end93function Base.show(io::IO, ::MIME"text/plain", basis::LobattoLegendreBasis)94@nospecialize basis # reduce precompilation time9596print(io, "LobattoLegendreBasis{", real(basis), "} with polynomials of degree ",97polydeg(basis))98return nothing99end100101function Base.:(==)(b1::LobattoLegendreBasis, b2::LobattoLegendreBasis)102if typeof(b1) != typeof(b2)103return false104end105106for field in fieldnames(typeof(b1))107if getfield(b1, field) != getfield(b2, field)108return false109end110end111112return true113end114115@inline Base.real(basis::LobattoLegendreBasis{RealT}) where {RealT} = RealT116117@inline function nnodes(basis::LobattoLegendreBasis{RealT, NNODES}) where {RealT, NNODES118}119return NNODES120end121122"""123eachnode(basis::LobattoLegendreBasis)124125Return an iterator over the indices that specify the location in relevant data structures126for the nodes in `basis`.127In particular, not the nodes themselves are returned.128"""129@inline eachnode(basis::LobattoLegendreBasis) = Base.OneTo(nnodes(basis))130131@inline polydeg(basis::LobattoLegendreBasis) = nnodes(basis) - 1132133@inline get_nodes(basis::LobattoLegendreBasis) = basis.nodes134135"""136integrate(f, u, basis::LobattoLegendreBasis)137138Map the function `f` to the coefficients `u` and integrate with respect to the139quadrature rule given by `basis`.140"""141function integrate(f, u, basis::LobattoLegendreBasis)142@unpack weights = basis143144res = zero(f(first(u)))145for i in eachindex(u, weights)146res += f(u[i]) * weights[i]147end148return res149end150151# Return the first/last weight of the quadrature associated with `basis`.152# Since the mass matrix for nodal Lobatto-Legendre bases is diagonal,153# these weights are the only coefficients necessary for the scaling of154# surface terms/integrals in DGSEM.155left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights)156right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights)157158struct LobattoLegendreMortarL2{RealT <: Real, NNODES,159ForwardMatrix <: AbstractMatrix{RealT},160ReverseMatrix <: AbstractMatrix{RealT}} <:161AbstractMortarL2{RealT}162forward_upper::ForwardMatrix163forward_lower::ForwardMatrix164reverse_upper::ReverseMatrix165reverse_lower::ReverseMatrix166end167168function Adapt.adapt_structure(to, mortar::LobattoLegendreMortarL2)169forward_upper = adapt(to, mortar.forward_upper)170forward_lower = adapt(to, mortar.forward_lower)171reverse_upper = adapt(to, mortar.reverse_upper)172reverse_lower = adapt(to, mortar.reverse_lower)173return LobattoLegendreMortarL2{eltype(forward_upper), nnodes(mortar),174typeof(forward_upper),175typeof(reverse_upper)}(forward_upper, forward_lower,176reverse_upper, reverse_lower)177end178179function MortarL2(basis::LobattoLegendreBasis)180RealT = real(basis)181nnodes_ = nnodes(basis)182183forward_upper = calc_forward_upper(nnodes_, RealT)184forward_lower = calc_forward_lower(nnodes_, RealT)185reverse_upper = calc_reverse_upper(nnodes_, Val(:gauss), RealT)186reverse_lower = calc_reverse_lower(nnodes_, Val(:gauss), RealT)187188# We keep the matrices above stored using the standard `Matrix` type189# since this is usually as fast as `SMatrix`190# (when using `let` in the volume integral/`@threaded`)191# and reduces latency192193# TODO: Taal performance194# Check the performance of different implementations of `mortar_fluxes_to_elements!`195# with different types of the reverse matrices and different types of196# `fstar_upper_threaded` etc. used in the cache.197# Check whether `@turbo` with `eachnode` in `multiply_dimensionwise!` can be faster than198# `@tullio` when the matrix sizes are not necessarily static.199# reverse_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_upper_)200# reverse_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_lower_)201202return LobattoLegendreMortarL2{RealT, nnodes_, typeof(forward_upper),203typeof(reverse_upper)}(forward_upper, forward_lower,204reverse_upper, reverse_lower)205end206207function Base.show(io::IO, mortar::LobattoLegendreMortarL2)208@nospecialize mortar # reduce precompilation time209210print(io, "LobattoLegendreMortarL2{", real(mortar), "}(polydeg=", polydeg(mortar),211")")212return nothing213end214function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarL2)215@nospecialize mortar # reduce precompilation time216217print(io, "LobattoLegendreMortarL2{", real(mortar), "} with polynomials of degree ",218polydeg(mortar))219return nothing220end221222@inline Base.real(mortar::LobattoLegendreMortarL2{RealT}) where {RealT} = RealT223224@inline function nnodes(mortar::LobattoLegendreMortarL2{RealT, NNODES}) where {RealT,225NNODES}226return NNODES227end228229@inline polydeg(mortar::LobattoLegendreMortarL2) = nnodes(mortar) - 1230231# TODO: We can create EC mortars along the lines of the following implementation.232# abstract type AbstractMortarEC{RealT} <: AbstractMortar{RealT} end233234# struct LobattoLegendreMortarEC{RealT<:Real, NNODES, MortarMatrix<:AbstractMatrix{RealT}, SurfaceFlux} <: AbstractMortarEC{RealT}235# forward_upper::MortarMatrix236# forward_lower::MortarMatrix237# reverse_upper::MortarMatrix238# reverse_lower::MortarMatrix239# surface_flux::SurfaceFlux240# end241242# function MortarEC(basis::LobattoLegendreBasis{RealT}, surface_flux)243# forward_upper = calc_forward_upper(n_nodes, RealT)244# forward_lower = calc_forward_lower(n_nodes, RealT)245# l2reverse_upper = calc_reverse_upper(n_nodes, Val(:gauss_lobatto), RealT)246# l2reverse_lower = calc_reverse_lower(n_nodes, Val(:gauss_lobatto), RealT)247248# # type conversions to make use of StaticArrays etc.249# nnodes_ = nnodes(basis)250# forward_upper = SMatrix{nnodes_, nnodes_}(forward_upper)251# forward_lower = SMatrix{nnodes_, nnodes_}(forward_lower)252# l2reverse_upper = SMatrix{nnodes_, nnodes_}(l2reverse_upper)253# l2reverse_lower = SMatrix{nnodes_, nnodes_}(l2reverse_lower)254255# LobattoLegendreMortarEC{RealT, nnodes_, typeof(forward_upper), typeof(surface_flux)}(256# forward_upper, forward_lower,257# l2reverse_upper, l2reverse_lower,258# surface_flux259# )260# end261262# @inline nnodes(mortar::LobattoLegendreMortarEC{RealT, NNODES}) = NNODES263264struct LobattoLegendreAnalyzer{RealT <: Real, NNODES,265VectorT <: AbstractVector{RealT},266Vandermonde <: AbstractMatrix{RealT}} <:267SolutionAnalyzer{RealT}268nodes::VectorT269weights::VectorT270vandermonde::Vandermonde271end272273function SolutionAnalyzer(basis::LobattoLegendreBasis;274analysis_polydeg = 2 * polydeg(basis))275RealT = real(basis)276nnodes_ = analysis_polydeg + 1277278nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_, RealT)279vandermonde = polynomial_interpolation_matrix(get_nodes(basis), nodes_)280281# Type conversions to enable possible optimizations of runtime performance282# and latency283nodes = SVector{nnodes_, RealT}(nodes_)284weights = SVector{nnodes_, RealT}(weights_)285286return LobattoLegendreAnalyzer{RealT, nnodes_, typeof(nodes), typeof(vandermonde)}(nodes,287weights,288vandermonde)289end290291function Base.show(io::IO, analyzer::LobattoLegendreAnalyzer)292@nospecialize analyzer # reduce precompilation time293294print(io, "LobattoLegendreAnalyzer{", real(analyzer), "}(polydeg=",295polydeg(analyzer), ")")296return nothing297end298function Base.show(io::IO, ::MIME"text/plain", analyzer::LobattoLegendreAnalyzer)299@nospecialize analyzer # reduce precompilation time300301print(io, "LobattoLegendreAnalyzer{", real(analyzer),302"} with polynomials of degree ", polydeg(analyzer))303return nothing304end305306@inline Base.real(analyzer::LobattoLegendreAnalyzer{RealT}) where {RealT} = RealT307308@inline function nnodes(analyzer::LobattoLegendreAnalyzer{RealT, NNODES}) where {RealT,309NNODES}310return NNODES311end312"""313eachnode(analyzer::LobattoLegendreAnalyzer)314315Return an iterator over the indices that specify the location in relevant data structures316for the nodes in `analyzer`.317In particular, not the nodes themselves are returned.318"""319@inline eachnode(analyzer::LobattoLegendreAnalyzer) = Base.OneTo(nnodes(analyzer))320321@inline polydeg(analyzer::LobattoLegendreAnalyzer) = nnodes(analyzer) - 1322323struct LobattoLegendreAdaptorL2{RealT <: Real, NNODES,324ForwardMatrix <: AbstractMatrix{RealT},325ReverseMatrix <: AbstractMatrix{RealT}} <:326AdaptorL2{RealT}327forward_upper::ForwardMatrix328forward_lower::ForwardMatrix329reverse_upper::ReverseMatrix330reverse_lower::ReverseMatrix331end332333function AdaptorL2(basis::LobattoLegendreBasis{RealT}) where {RealT}334nnodes_ = nnodes(basis)335336forward_upper_ = calc_forward_upper(nnodes_, RealT)337forward_lower_ = calc_forward_lower(nnodes_, RealT)338reverse_upper_ = calc_reverse_upper(nnodes_, Val(:gauss), RealT)339reverse_lower_ = calc_reverse_lower(nnodes_, Val(:gauss), RealT)340341# type conversions to get the requested real type and enable possible342# optimizations of runtime performance and latency343344# TODO: Taal performance345# Check the performance of different implementations of346# `refine_elements!` (forward) and `coarsen_elements!` (reverse)347# with different types of the matrices.348# Check whether `@turbo` with `eachnode` in `multiply_dimensionwise!`349# can be faster than `@tullio` when the matrix sizes are not necessarily350# static.351forward_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(forward_upper_)352forward_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(forward_lower_)353# forward_upper = Matrix{RealT}(forward_upper_)354# forward_lower = Matrix{RealT}(forward_lower_)355356reverse_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_upper_)357reverse_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_lower_)358# reverse_upper = Matrix{RealT}(reverse_upper_)359# reverse_lower = Matrix{RealT}(reverse_lower_)360361return LobattoLegendreAdaptorL2{RealT, nnodes_, typeof(forward_upper),362typeof(reverse_upper)}(forward_upper, forward_lower,363reverse_upper, reverse_lower)364end365366function Base.show(io::IO, adaptor::LobattoLegendreAdaptorL2)367@nospecialize adaptor # reduce precompilation time368369print(io, "LobattoLegendreAdaptorL2{", real(adaptor), "}(polydeg=",370polydeg(adaptor), ")")371return nothing372end373function Base.show(io::IO, ::MIME"text/plain", adaptor::LobattoLegendreAdaptorL2)374@nospecialize adaptor # reduce precompilation time375376print(io, "LobattoLegendreAdaptorL2{", real(adaptor),377"} with polynomials of degree ", polydeg(adaptor))378return nothing379end380381@inline Base.real(adaptor::LobattoLegendreAdaptorL2{RealT}) where {RealT} = RealT382383@inline function nnodes(adaptor::LobattoLegendreAdaptorL2{RealT, NNODES}) where {RealT,384NNODES}385return NNODES386end387388@inline polydeg(adaptor::LobattoLegendreAdaptorL2) = nnodes(adaptor) - 1389390###############################################################################391# Polynomial derivative and interpolation functions392393# TODO: Taal refactor, allow other RealT below and adapt constructors above accordingly394395# Calculate the Dhat matrix = -M^{-1} D^T M for weak form differentiation.396# Note that this is the negated version of the matrix that shows up on the RHS of the397# DG update multiplying the physical flux evaluations.398function calc_Dhat(derivative_matrix, weights)399n_nodes = length(weights)400Dhat = Matrix(derivative_matrix') # D^T401402# Perform M matrix multiplications and negate403for n in 1:n_nodes, j in 1:n_nodes404Dhat[j, n] *= -weights[n] / weights[j]405end406407return Dhat408end409410# Calculate the Dsplit matrix for split-form differentiation: Dsplit = 2D - M⁻¹B411# Note that this is the negated version of the matrix that shows up on the RHS of the412# DG update multiplying the two-point numerical volume flux evaluations.413function calc_Dsplit(derivative_matrix, weights)414# Start with 2 x the normal D matrix415Dsplit = 2 .* derivative_matrix416417# Modify to account for the weighted boundary terms418Dsplit[1, 1] += 1 / weights[1] # B[1, 1] = -1419Dsplit[end, end] -= 1 / weights[end] # B[end, end] = 1420421return Dsplit422end423424# Calculate the polynomial derivative matrix D.425# This implements algorithm 37 "PolynomialDerivativeMatrix" from Kopriva's book.426function polynomial_derivative_matrix(nodes)427n_nodes = length(nodes)428D = zeros(eltype(nodes), n_nodes, n_nodes)429wbary = barycentric_weights(nodes)430431for i in 1:n_nodes, j in 1:n_nodes432if j != i433D[i, j] = (wbary[j] / wbary[i]) * 1 / (nodes[i] - nodes[j])434D[i, i] -= D[i, j]435end436end437438return D439end440441# Calculate and interpolation matrix (Vandermonde matrix) between two given sets of nodes442# See algorithm 32 "PolynomialInterpolationMatrix" from Kopriva's book.443function polynomial_interpolation_matrix(nodes_in, nodes_out,444baryweights_in = barycentric_weights(nodes_in))445n_nodes_in = length(nodes_in)446n_nodes_out = length(nodes_out)447vandermonde = Matrix{promote_type(eltype(nodes_in), eltype(nodes_out))}(undef,448n_nodes_out,449n_nodes_in)450polynomial_interpolation_matrix!(vandermonde, nodes_in, nodes_out, baryweights_in)451452return vandermonde453end454455# This implements algorithm 32 "PolynomialInterpolationMatrix" from Kopriva's book.456function polynomial_interpolation_matrix!(vandermonde,457nodes_in, nodes_out,458baryweights_in)459fill!(vandermonde, zero(eltype(vandermonde)))460461for k in eachindex(nodes_out)462match = false463for j in eachindex(nodes_in)464if isapprox(nodes_out[k], nodes_in[j])465match = true466vandermonde[k, j] = 1467end468end469470if match == false471s = zero(eltype(vandermonde))472for j in eachindex(nodes_in)473t = baryweights_in[j] / (nodes_out[k] - nodes_in[j])474vandermonde[k, j] = t475s += t476end477for j in eachindex(nodes_in)478vandermonde[k, j] = vandermonde[k, j] / s479end480end481end482483return vandermonde484end485486"""487barycentric_weights(nodes)488489Calculate the barycentric weights for a given node distribution, i.e.,490```math491w_j = \\frac{1}{ \\prod_{k \\neq j} \\left( x_j - x_k \\right ) }492```493494For details, see (especially Section 3)495- Jean-Paul Berrut and Lloyd N. Trefethen (2004).496Barycentric Lagrange Interpolation.497[DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715)498"""499function barycentric_weights(nodes)500n_nodes = length(nodes)501weights = ones(eltype(nodes), n_nodes)502503for j in 2:n_nodes, k in 1:(j - 1)504weights[k] *= nodes[k] - nodes[j]505weights[j] *= nodes[j] - nodes[k]506end507508for j in 1:n_nodes509weights[j] = 1 / weights[j]510end511512return weights513end514515"""516lagrange_interpolating_polynomials(x, nodes, wbary)517518Calculate Lagrange polynomials for a given node distribution with519associated barycentric weights `wbary` at a given point `x` on the520reference interval ``[-1, 1]``.521522This returns all ``l_j(x)``, i.e., the Lagrange polynomials for each node ``x_j``.523Thus, to obtain the interpolating polynomial ``p(x)`` at ``x``, one has to524multiply the Lagrange polynomials with the nodal values ``u_j`` and sum them up:525``p(x) = \\sum_{j=1}^{n} u_j l_j(x)``.526527For details, see e.g. Section 2 of528- Jean-Paul Berrut and Lloyd N. Trefethen (2004).529Barycentric Lagrange Interpolation.530[DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715)531"""532function lagrange_interpolating_polynomials(x, nodes, wbary)533n_nodes = length(nodes)534polynomials = zeros(eltype(nodes), n_nodes)535536for i in 1:n_nodes537# Avoid division by zero when `x` is close to node by using538# the Kronecker-delta property at nodes539# of the Lagrange interpolation polynomials.540if isapprox(x, nodes[i], rtol = eps(x))541polynomials[i] = 1542return polynomials543end544end545546for i in 1:n_nodes547polynomials[i] = wbary[i] / (x - nodes[i])548end549total = sum(polynomials)550551for i in 1:n_nodes552polynomials[i] /= total553end554555return polynomials556end557558"""559gauss_lobatto_nodes_weights(n_nodes::Integer, RealT = Float64)560561Computes nodes ``x_j`` and weights ``w_j`` for the (Legendre-)Gauss-Lobatto quadrature.562This implements algorithm 25 "GaussLobattoNodesAndWeights" from the book563564- David A. Kopriva, (2009).565Implementing spectral methods for partial differential equations:566Algorithms for scientists and engineers.567[DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5)568"""569function gauss_lobatto_nodes_weights(n_nodes::Integer, RealT = Float64)570# From FLUXO (but really from blue book by Kopriva)571n_iterations = 20572tolerance = 2 * eps(RealT) # Relative tolerance for Newton iteration573574# Initialize output575nodes = zeros(RealT, n_nodes)576weights = zeros(RealT, n_nodes)577578# Special case for polynomial degree zero (first order finite volume):579# Fall back to Gauss-Legendre580if n_nodes == 1581nodes[1] = 0582weights[1] = 2583return nodes, weights584end585586# Get polynomial degree for convenience587N = n_nodes - 1588589# Calculate values at boundary590nodes[1] = -1591nodes[end] = 1592weights[1] = RealT(2) / (N * (N + 1))593weights[end] = weights[1]594595# Calculate interior values596if N > 1597cont1 = convert(RealT, pi) / N598cont2 = 3 / (8 * N * convert(RealT, pi))599600# Use symmetry -> only left side is computed601for i in 1:(div(N + 1, 2) - 1)602# Calculate node603# Initial guess for Newton method604nodes[i + 1] = -cos(cont1 * (i + 0.25) - cont2 / (i + 0.25))605606# Newton iteration to find root of Legendre polynomial (= integration node)607for k in 0:n_iterations608q, qder, _ = calc_q_and_l(N, nodes[i + 1])609dx = -q / qder610nodes[i + 1] += dx611if abs(dx) < tolerance * abs(nodes[i + 1])612break613end614615if k == n_iterations616@warn "`gauss_lobatto_nodes_weights` Newton iteration did not converge"617end618end619620# Calculate weight621_, _, L = calc_q_and_l(N, nodes[i + 1])622weights[i + 1] = weights[1] / L^2623624# Set nodes and weights according to symmetry properties625nodes[N + 1 - i] = -nodes[i + 1]626weights[N + 1 - i] = weights[i + 1]627end628end629630# If odd number of nodes, set center node to origin (= 0.0) and calculate weight631if n_nodes % 2 == 1632_, _, L = calc_q_and_l(N, zero(RealT))633nodes[div(N, 2) + 1] = 0634weights[div(N, 2) + 1] = weights[1] / L^2635end636637return nodes, weights638end639640# From FLUXO (but really from blue book by Kopriva, algorithm 24)641function calc_q_and_l(N::Integer, x::Real)642RealT = typeof(x)643644L_Nm2 = one(RealT)645L_Nm1 = x646Lder_Nm2 = zero(RealT)647Lder_Nm1 = one(RealT)648649local L650for i in 2:N651L = ((2 * i - 1) * x * L_Nm1 - (i - 1) * L_Nm2) / i652Lder = Lder_Nm2 + (2 * i - 1) * L_Nm1653L_Nm2 = L_Nm1654L_Nm1 = L655Lder_Nm2 = Lder_Nm1656Lder_Nm1 = Lder657end658659q = (2 * N + 1) / (N + 1) * (x * L - L_Nm2)660qder = (2 * N + 1) * L661662return q, qder, L663end664665"""666legendre_polynomial_and_derivative(N::Int, x::Real)667668Computes the Legendre polynomial of degree `N` and its derivative at `x`.669This implements algorithm 22 "LegendrePolynomialAndDerivative" from the book670671- David A. Kopriva, (2009).672Implementing spectral methods for partial differential equations:673Algorithms for scientists and engineers.674[DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5)675"""676function legendre_polynomial_and_derivative(N::Int, x::Real)677RealT = typeof(x)678if N == 0679poly = one(RealT)680deriv = zero(RealT)681elseif N == 1682poly = x683deriv = one(RealT)684else685poly_Nm2 = one(RealT)686poly_Nm1 = copy(x)687deriv_Nm2 = zero(RealT)688deriv_Nm1 = one(RealT)689690poly = zero(RealT)691deriv = zero(RealT)692for i in 2:N693poly = ((2 * i - 1) * x * poly_Nm1 - (i - 1) * poly_Nm2) / i694deriv = deriv_Nm2 + (2 * i - 1) * poly_Nm1695poly_Nm2 = poly_Nm1696poly_Nm1 = poly697deriv_Nm2 = deriv_Nm1698deriv_Nm1 = deriv699end700end701702# Normalize703poly = poly * sqrt(N + 0.5)704deriv = deriv * sqrt(N + 0.5)705706return poly, deriv707end708709# Calculate Legendre vandermonde matrix and its inverse710function vandermonde_legendre(nodes, N::Integer, RealT = Float64)711n_nodes = length(nodes)712n_modes = N + 1713vandermonde = zeros(RealT, n_nodes, n_modes)714715for i in 1:n_nodes716for m in 1:n_modes717vandermonde[i, m], _ = legendre_polynomial_and_derivative(m - 1, nodes[i])718end719end720# for very high polynomial degree, this is not well conditioned721inverse_vandermonde = inv(vandermonde)722return vandermonde, inverse_vandermonde723end724vandermonde_legendre(nodes, RealT = Float64) = vandermonde_legendre(nodes,725length(nodes) - 1,726RealT)727end # @muladd728729730