Skip to content

Commit 3dbb5db

Browse files
authored
Implement gausslobatto / gaussradau and Monic (#199)
* radau/lobatto * Take the first iteration out of the loop to avoid if * Implement monic * typo * Recurrence coefficients * Doesn't really do much * Typo
1 parent cc668ce commit 3dbb5db

File tree

7 files changed

+237
-3
lines changed

7 files changed

+237
-3
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ClassicalOrthogonalPolynomials"
22
uuid = "b30e2e7b-c4ee-47da-9d5f-2c5c27239acd"
33
authors = ["Sheehan Olver <[email protected]>"]
4-
version = "0.13.4"
4+
version = "0.13.5"
55

66
[deps]
77
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"

src/ClassicalOrthogonalPolynomials.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ end
285285
include("clenshaw.jl")
286286
include("ratios.jl")
287287
include("normalized.jl")
288+
include("monic.jl")
288289
include("lanczos.jl")
289290
include("choleskyQR.jl")
290291

@@ -318,6 +319,50 @@ end
318319

319320
golubwelsch(V::SubQuasiArray) = golubwelsch(parent(V), maximum(parentindices(V)[2]))
320321

322+
function gaussradau(P::Monic{T}, n::Integer, endpt) where {T}
323+
## See Thm 3.2 in Gautschi (2004)'s book
324+
# n is number of interior points
325+
J = jacobimatrix(P.P, n + 1)
326+
α, β = diagonaldata(J), supdiagonaldata(J)
327+
endpt = T(endpt)
328+
p0, p1 = P[endpt,n:n+1]
329+
a = endpt - β[end]^2 * p0 / p1
330+
α′ = vcat(@view(α[begin:end-1]), a)
331+
J′ = SymTridiagonal(α′, β)
332+
x, w = golubwelsch(J′) # not inferred
333+
w .*= sum(orthogonalityweight(P))
334+
if endpt axes(P, 1)[begin]
335+
x[1] = endpt # avoid rounding errors
336+
else
337+
x[end] = endpt
338+
end
339+
return x, w
340+
end
341+
gaussradau(P, n::Integer, endpt) = gaussradau(Monic(P), n, endpt)
342+
343+
function gausslobatto(P::Monic{T}, n::Integer) where {T}
344+
## See Thm 3.6 of Gautschi (2004)'s book
345+
# n is the number of interior points
346+
a, b = axes(P, 1)[begin], axes(P, 1)[end]
347+
J = jacobimatrix(P.P, n + 2)
348+
α, β = diagonaldata(J), supdiagonaldata(J)
349+
p0a, p1a = P[a, (n+1):(n+2)]
350+
p0b, p1b = P[b, (n+1):(n+2)]
351+
# Solve Eq. 3.1.2.8
352+
Δ = p1a * p0b - p1b * p0a # This could underflow/overflow for large n
353+
aext = (a * p1a * p0b - b * p1b * p0a) / Δ
354+
bext = (b - a) * p1a * p1b / Δ
355+
α′ = vcat(@view(α[begin:end-1]), aext)
356+
β′ = vcat(@view(β[begin:end-1]), sqrt(bext)) # LazyBandedMatrices doesn't like when we use Vcat(array, scalar) apparently
357+
J′ = LazyBandedMatrices.SymTridiagonal(α′, β′)
358+
x, w = golubwelsch(J′)
359+
w .*= sum(orthogonalityweight(P))
360+
x[1] = a
361+
x[end] = b # avoid rounding errors. Doesn't affect the actual result but avoids e.g. domain errors
362+
return x, w
363+
end
364+
gausslobatto(P, n::Integer) = gausslobatto(Monic(P), n)
365+
321366
# Default is Golub–Welsch expansion
322367
# note this computes the grid an extra time.
323368
function plan_transform_layout(::AbstractOPLayout, P, szs::NTuple{N,Int}, dims=ntuple(identity,Val(N))) where N

src/monic.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
struct Monic{T,OPs<:AbstractQuasiMatrix{T},NL} <: OrthogonalPolynomial{T}
2+
P::Normalized{T,OPs,NL}
3+
α::AbstractVector{T} # diagonal of Jacobi matrix of P
4+
β::AbstractVector{T} # squared supdiagonal of Jacobi matrix of P
5+
end
6+
7+
Monic(P::AbstractQuasiMatrix) = Monic(Normalized(P))
8+
function Monic(P::Normalized)
9+
X = jacobimatrix(P)
10+
α = diagonaldata(X)
11+
β = supdiagonaldata(X)
12+
return Monic(P, α, β.^2)
13+
end
14+
Monic(P::Monic) = Monic(P.P, P.α, P.β)
15+
16+
Normalized(P::Monic) = P.P
17+
18+
axes(P::Monic) = axes(P.P)
19+
20+
orthogonalityweight(P::Monic) = orthogonalityweight(P.P)
21+
22+
_p0(::Monic{T}) where {T} = one(T)
23+
24+
show(io::IO, P::Monic) = print(io, "Monic($(P.P.P))")
25+
show(io::IO, ::MIME"text/plain", P::Monic) = show(io, P)
26+
27+
function recurrencecoefficients(P::Monic{T}) where {T}
28+
α = P.α
29+
β = P.β
30+
return _monicrecurrencecoefficients(α, β) # function barrier
31+
end
32+
function _monicrecurrencecoefficients::AbstractVector{T}, β) where {T}
33+
A = Ones{T}(∞)
34+
B = -α
35+
C = Vcat(zero(T), β)
36+
return A, B, C
37+
end
38+

test/runtests.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ include("test_fourier.jl")
3939
include("test_odes.jl")
4040
include("test_ratios.jl")
4141
include("test_normalized.jl")
42+
include("test_monic.jl")
4243
include("test_lanczos.jl")
4344
include("test_interlace.jl")
4445
include("test_choleskyQR.jl")
4546
include("test_roots.jl")
47+
include("test_lobattoradau.jl")
4648

4749
@testset "Auto-diff" begin
4850
U = Ultraspherical(1)
@@ -83,4 +85,4 @@ end
8385
@testset "Issue #179" begin
8486
@test startswith(sprint(show, MIME"text/plain"(), Chebyshev()[0.3, :]; context=(:compact=>true, :limit=>true)), "ℵ₀-element view(::ChebyshevT{Float64}, 0.3, :)")
8587
@test startswith(sprint(show, MIME"text/plain"(), Jacobi(0.2, 0.5)[-0.7, :]; context=(:compact=>true, :limit=>true)), "ℵ₀-element view(::Jacobi{Float64}, -0.7, :)")
86-
end
88+
end

test/test_jacobi.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ import ClassicalOrthogonalPolynomials: recurrencecoefficients, basis, MulQuasiMa
8181

8282
M = P[x,1:3]'Diagonal(w)*P[x,1:3]
8383
@test M Diagonal(M)
84-
x,w = gaussradau(3,a,b)
84+
x,w = FastGaussQuadrature.gaussradau(3,a,b)
8585
M = P[x,1:3]'Diagonal(w)*P[x,1:3]
8686
@test M Diagonal(M)
8787

test/test_lobattoradau.jl

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using ClassicalOrthogonalPolynomials, FastGaussQuadrature
2+
const COP = ClassicalOrthogonalPolynomials
3+
const FGQ = FastGaussQuadrature
4+
using Test
5+
using ClassicalOrthogonalPolynomials: symtridiagonalize
6+
using LinearAlgebra
7+
8+
@testset "gaussradau" begin
9+
@testset "Compare with FastGaussQuadrature" begin
10+
x1, w1 = COP.gaussradau(Legendre(), 5, -1.0)
11+
x2, w2 = FGQ.gaussradau(6)
12+
@test x1 x2 && w1 w2
13+
@test x1[1] == -1
14+
15+
x1, w1 = COP.gaussradau(Jacobi(1.0, 3.5), 25, -1.0)
16+
x2, w2 = FGQ.gaussradau(26, 1.0, 3.5)
17+
@test x1 x2 && w1 w2
18+
@test x1[1] == -1
19+
20+
I0, I1 = COP.ChebyshevInterval(), COP.UnitInterval()
21+
P = Jacobi(2.0, 0.0)[COP.affine(I1, I0), :]
22+
x1, w1 = COP.gaussradau(P, 18, 0.0)
23+
x2, w2 = FGQ.gaussradau(19, 2.0, 0.0)
24+
@test 2x1 .- 1 x2 && 2w1 w2
25+
26+
x1, w1 = COP.gaussradau(Jacobi(1 / 2, 1 / 2), 4, 1.0)
27+
x2, w2 = FGQ.gaussradau(5, 1 / 2, 1 / 2)
28+
@test sort(-x1) x2
29+
@test_broken w1 w2 # What happens to the weights when inverting the interval?
30+
end
31+
32+
@testset "Example 3.5 in Gautschi (2004)'s book" begin
33+
P = Laguerre(3.0)
34+
n = 5
35+
J = symtridiagonalize(jacobimatrix(P))[1:(n-1), 1:(n-1)]
36+
_J = zeros(n, n)
37+
_J[1:n-1, 1:n-1] .= J
38+
_J[n-1, n] = sqrt((n - 1) * (n - 1 + P.α))
39+
_J[n, n-1] = _J[n-1, n]
40+
_J[n, n] = n - 1
41+
x, V = eigen(_J)
42+
w = 6V[1, :] .^ 2
43+
xx, ww = COP.gaussradau(P, n - 1, 0.0)
44+
@test xx x && ww w
45+
end
46+
47+
@testset "Some numerical integration" begin
48+
f = x -> 2x + 7x^2 + 10x^3 + exp(-x)
49+
x, w = COP.gaussradau(Chebyshev(), 10, -1.0)
50+
@test dot(f.(x), w) 14.97303754807069897 # integral of (2x + 7x^2 + 10x^3 + exp(-x))/sqrt(1-x^2)
51+
@test x[1] == -1
52+
53+
f = x -> -1.0 + 5x^6
54+
x, w = COP.gaussradau(Jacobi(-1/2, -1/2), 2, 1.0)
55+
@test dot(f.(x), w) 9π/16
56+
@test x[end] == 1
57+
@test length(x) == 3
58+
end
59+
end
60+
61+
@testset "gausslobatto" begin
62+
@testset "Compare with FastGaussQuadrature" begin
63+
x1, w1 = COP.gausslobatto(Legendre(), 5)
64+
x2, w2 = FGQ.gausslobatto(7)
65+
@test x1 x2 && w1 w2
66+
@test x1[1] == -1
67+
@test x1[end] == 1
68+
69+
I0, I1 = COP.ChebyshevInterval(), COP.UnitInterval()
70+
P = Legendre()[COP.affine(I1, I0), :]
71+
x1, w1 = COP.gausslobatto(P, 18)
72+
x2, w2 = FGQ.gausslobatto(20)
73+
@test 2x1 .- 1 x2 && 2w1 w2
74+
end
75+
76+
@testset "Some numerical integration" begin
77+
f = x -> 2x + 7x^2 + 10x^3 + exp(-x)
78+
x, w = COP.gausslobatto(Chebyshev(), 10)
79+
@test dot(f.(x), w) 14.97303754807069897
80+
@test x[1] == -1
81+
@test x[end] == 1
82+
@test length(x) == 12
83+
84+
f = x -> -1.0 + 5x^6
85+
x, w = COP.gausslobatto(Jacobi(-1/2, -1/2), 4)
86+
@test dot(f.(x), w) 9π/16
87+
@test x[1]==-1
88+
@test x[end] == 1
89+
@test length(x) == 6
90+
end
91+
end

test/test_monic.jl

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using ClassicalOrthogonalPolynomials
2+
using Test
3+
using ClassicalOrthogonalPolynomials: Monic, _p0, orthogonalityweight, recurrencecoefficients
4+
5+
@testset "Basic definition" begin
6+
P1 = Legendre()
7+
P2 = Normalized(P1)
8+
P3 = Monic(P1)
9+
@test P3.P == P2
10+
@test Monic(P3) === P3
11+
@test axes(P3) == axes(Legendre())
12+
@test Normalized(P3) === P3.P
13+
@test _p0(P3) == 1
14+
@test orthogonalityweight(P3) == orthogonalityweight(P1)
15+
@test sprint(show, MIME"text/plain"(), P3) == "Monic(Legendre())"
16+
end
17+
18+
@testset "evaluation" begin
19+
function _pochhammer(x, n)
20+
y = one(x)
21+
for i in 0:(n-1)
22+
y *= (x + i)
23+
end
24+
return y
25+
end
26+
jacobi_kn = (α, β, n) -> _pochhammer(n + α + β + 1, n) / (2.0^n * factorial(n))
27+
ultra_kn = (λ, n) -> 2^n * _pochhammer(λ, n) / factorial(n)
28+
chebt_kn = n -> n == 0 ? 1.0 : 2.0 .^ (n - 1)
29+
chebu_kn = n -> 2.0^n
30+
leg_kn = n -> 2.0^n * _pochhammer(1 / 2, n) / factorial(n)
31+
lag_kn = n -> (-1)^n / factorial(n)
32+
herm_kn = n -> 2.0^n
33+
_Jacobi(α, β, x, n) = Jacobi(α, β)[x, n+1] / jacobi_kn(α, β, n)
34+
_Ultraspherical(λ, x, n) = Ultraspherical(λ)[x, n+1] / ultra_kn(λ, n)
35+
_ChebyshevT(x, n) = ChebyshevT()[x, n+1] / chebt_kn(n)
36+
_ChebyshevU(x, n) = ChebyshevU()[x, n+1] / chebu_kn(n)
37+
_Legendre(x, n) = Legendre()[x, n+1] / leg_kn(n)
38+
_Laguerre(α, x, n) = Laguerre(α)[x, n+1] / lag_kn(n)
39+
_Hermite(x, n) = Hermite()[x, n+1] / herm_kn(n)
40+
Ps = [
41+
Jacobi(2.0, 5.0) (x, n)->_Jacobi(2.0, 5.0, x, n)
42+
Ultraspherical(1.7) (x, n)->_Ultraspherical(1.7, x, n)
43+
ChebyshevT() _ChebyshevT
44+
ChebyshevU() _ChebyshevU
45+
Legendre() _Legendre
46+
Laguerre(1.5) (x, n)->_Laguerre(1.5, x, n)
47+
Hermite() _Hermite
48+
]
49+
for (P, _P) in eachrow(Ps)
50+
Q = Monic(P)
51+
@test Q[0.2, 1] == 1.0
52+
@test Q[0.25, 2] _P(0.25, 1)
53+
@test Q[0.17, 3] _P(0.17, 2)
54+
@test Q[0.4, 17] _P(0.4, 16)
55+
@test Q[0.9, 21] _P(0.9, 20)
56+
# @inferred Q[0.2, 5] # no longer inferred
57+
end
58+
end

0 commit comments

Comments
 (0)