Skip to content

Commit 27fc2e2

Browse files
committed
Implement monic
1 parent d6f9375 commit 27fc2e2

File tree

4 files changed

+125
-43
lines changed

4 files changed

+125
-43
lines changed

src/ClassicalOrthogonalPolynomials.jl

Lines changed: 24 additions & 43 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,70 +319,50 @@ end
318319

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

321-
function gaussradau(P, n::Integer, endpt)
322-
## See Thm 3.2 in Gautschi (2004)'s book
323-
# n is the number of interior points
324-
J = symtridiagonalize(jacobimatrix(P, n + 1))
325-
α = diagonaldata(J)
326-
β = supdiagonaldata(J)
327-
T = eltype(P)
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)
328327
endpt = T(endpt)
329-
p0 = one(T)
330-
p1 = (endpt - α[1]) * p0
331-
for i in 2:n
332-
# Evaluate the monic polynomials πₙ₋₁(endpt), πₙ(endpt)
333-
_p1 = p0
334-
p0 = p1
335-
p1 = (endpt - α[i]) * p0 - β[i - 1]^2 * _p1
336-
end
337-
a = endpt - β[end]^2 * p0 / p1
328+
p0 = P[endpt, n] # πₙ₋₁
329+
p1 = P[endpt, n+1] # πₙ. Could be faster by computing p1 from p0 and πₙ₋₂, but the cost is tiny relative to eigen()
330+
a = (endpt - β[end]^2 * p0 / p1)::T
338331
α′ = vcat(@view(α[begin:end-1]), a)
339332
J′ = SymTridiagonal(α′, β)
340-
x, w = golubwelsch(J′)
333+
x, w = golubwelsch(J′) # not inferred
341334
w .*= sum(orthogonalityweight(P))
342335
if endpt axes(P, 1)[begin]
343336
x[1] = endpt # avoid rounding errors
344-
else
345-
x[end] = endpt
337+
else
338+
x[end] = endpt
346339
end
347340
return x, w
348341
end
342+
gaussradau(P, n::Integer, endpt) = gaussradau(Monic(P), n, endpt)
349343

350-
function gausslobatto(P, n::Integer)
351-
## See Thm 3.6 of Gautschi (2004)'s book
344+
function gausslobatto(P::Monic{T}, n::Integer) where {T}
345+
## See Thm 3.6 of Gautschi (2004)'s book
352346
# n is the number of interior points
353347
a, b = axes(P, 1)[begin], axes(P, 1)[end]
354-
J = symtridiagonalize(jacobimatrix(P, n + 2))
355-
α = diagonaldata(J)
356-
β = supdiagonaldata(J)
357-
T = eltype(P)
358-
a, b = T(a), T(b)
359-
p0a = one(T)
360-
p0b = one(T)
361-
p1a = (a - α[1]) * p0a
362-
p1b = (b - α[1]) * p0b
363-
for i in 2:(n+1)
364-
# Evaluate the monic polynomials πₙ₋₁(a), πₙ₋₁(b), πₙ(a), πₙ(b)
365-
_p1a = p0a
366-
_p1b = p0b
367-
p0a = p1a
368-
p0b = p1b
369-
p1a = (a - α[i]) * p0a - β[i - 1]^2 * _p1a
370-
p1b = (b - α[i]) * p0b - β[i - 1]^2 * _p1b
371-
end
348+
J = jacobimatrix(P.P, n + 2)
349+
α, β = diagonaldata(J), supdiagonaldata(J)
350+
p0a, p0b = P[a, n+1], P[b, n+1]
351+
p1a, p1b = P[a, n+2], P[b, n+2]
372352
# Solve Eq. 3.1.2.8
373353
Δ = p1a * p0b - p1b * p0a # This could underflow/overflow for large n
374-
aext = (a * p1a * p0b - b * p1b * p0a) / Δ
375-
bext = (b - a) * p1a * p1b / Δ
354+
aext = (a * p1a * p0b - b * p1b * p0a) / Δ
355+
bext = (b - a) * p1a * p1b / Δ
376356
α′ = vcat(@view(α[begin:end-1]), aext)
377357
β′ = vcat(@view(β[begin:end-1]), sqrt(bext)) # LazyBandedMatrices doesn't like when we use Vcat(array, scalar) apparently
378358
J′ = LazyBandedMatrices.SymTridiagonal(α′, β′)
379359
x, w = golubwelsch(J′)
380360
w .*= sum(orthogonalityweight(P))
381-
x[1] = a
361+
x[1] = a
382362
x[end] = b # avoid rounding errors. Doesn't affect the actual result but avoids e.g. domain errors
383-
return x, w
363+
return x, w
384364
end
365+
gausslobatto(P, n::Integer) = gausslobatto(Monic(P), n)
385366

386367
# Default is Golub–Welsch expansion
387368
# note this computes the grid an extra time.

src/monic.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
struct Monic{T,OPs<:AbstractQuasiMatrix{T},NL} <: OrthogonalPolynomial{T}
2+
P::Normalized{T,OPs,NL}
3+
# X::AbstractMatrix{T} # Should X be stored? Probably not.
4+
# X would be the Jacobi matrix of the normalised polynomials, not for the monic polynomials.
5+
end
6+
# Will need to figure out what this should be exactly.
7+
# Consider this internal for now until it stabilises.
8+
9+
Monic(P::AbstractQuasiMatrix) = Monic(Normalized(P))
10+
Monic(P::Monic) = P
11+
12+
Normalized(P::Monic) = P.P
13+
14+
axes(P::Monic) = axes(P.P)
15+
16+
orthogonalityweight(P::Monic) = orthogonalityweight(P.P)
17+
18+
_p0(::Monic{T}) where {T} = one(T)
19+
20+
show(io::IO, P::Monic) = print(io, "Monic($(P.P.P))")
21+
show(io::IO, ::MIME"text/plain", P::Monic) = show(io, P)
22+
23+
function getindex(P::Monic{T}, x::Number, n::Int)::T where {T}
24+
# TODO: Rewrite this to be more efficient using forwardrecurrence!
25+
p0 = _p0(P)
26+
n == 1 && return p0
27+
t = convert(T, x)
28+
J = jacobimatrix(P.P, n)
29+
α = diagonaldata(J)
30+
β = supdiagonaldata(J)
31+
p1 = ((t - α[1]) * p0)::T
32+
n == 2 && return p1
33+
for i in 2:(n-1)
34+
_p1 = p0::T
35+
p0 = p1::T
36+
p1 = ((t - α[i]) * p1 - β[i-1]^2 * _p1)::T
37+
end
38+
return p1
39+
end
40+
# Should a method be written that makes this more efficient when requesting multiple n?
41+
42+

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ 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")

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
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 "getindex" 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.4, 5]
57+
end
58+
end

0 commit comments

Comments
 (0)