Skip to content

Commit 3563c77

Browse files
authored
Merge branch 'master' into master
2 parents b222844 + 0f1b2d3 commit 3563c77

File tree

5 files changed

+113
-55
lines changed

5 files changed

+113
-55
lines changed

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SemiclassicalOrthogonalPolynomials"
22
uuid = "291c01f3-23f6-4eb6-aeb0-063a639b53f2"
33
authors = ["Sheehan Olver <[email protected]>"]
4-
version = "0.3.6"
4+
version = "0.4.0"
55

66
[deps]
77
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
@@ -21,15 +21,15 @@ SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
2121
[compat]
2222
ArrayLayouts = "1"
2323
BandedMatrices = "0.17"
24-
ClassicalOrthogonalPolynomials = "0.9, 0.10"
24+
ClassicalOrthogonalPolynomials = "0.10.1"
2525
ContinuumArrays = "0.12, 0.13"
2626
FillArrays = "1"
2727
HypergeometricFunctions = "0.3.4"
2828
InfiniteArrays = "0.12.9"
2929
InfiniteLinearAlgebra = "0.6.19"
3030
LazyArrays = "1"
3131
QuasiArrays = "0.9, 0.10"
32-
SingularIntegrals = "0.0.2, 0.1"
32+
SingularIntegrals = "0.1"
3333
SpecialFunctions = "1.0, 2"
3434
julia = "1.9"
3535

src/SemiclassicalOrthogonalPolynomials.jl

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ function semiclassical_jacobimatrix(t, a, b, c)
146146
iszero(c) && return jacobimatrix(P)
147147
if isone(c)
148148
return cholesky_jacobimatrix(x->(t-x),P)
149-
elseif isone(c÷2)
149+
elseif isone(c/2)
150150
return qr_jacobimatrix(x->(t-x),P)
151151
elseif isinteger(c) && c 0 # reduce other integer c cases to hierarchy
152-
return SemiclassicalJacobi.(t, a, b, 0:c)[end].X
152+
return SemiclassicalJacobi.(t, a, b, 0:Int(c))[end].X
153153
else # if c is not an integer, use Lanczos for now
154154
x = axes(P,1)
155155
return jacobimatrix(LanczosPolynomial(@.(x^a * (1-x)^b * (t-x)^c), jacobi(b, a, UnitInterval{T}())))
@@ -170,11 +170,11 @@ function semiclassical_jacobimatrix(Q::SemiclassicalJacobi, a, b, c)
170170
return Q.X
171171
end
172172

173-
if isone(Δa÷2) && iszero(Δb) && iszero(Δc) # raising by 2
173+
if isone(Δa/2) && iszero(Δb) && iszero(Δc) # raising by 2
174174
qr_jacobimatrix(Q.X,Q)
175-
elseif iszero(Δa) && isone(Δb÷2) && iszero(Δc)
175+
elseif iszero(Δa) && isone(Δb/2) && iszero(Δc)
176176
qr_jacobimatrix(I-Q.X,Q)
177-
elseif iszero(Δa) && iszero(Δb) && isone(Δc÷2)
177+
elseif iszero(Δa) && iszero(Δb) && isone(Δc/2)
178178
qr_jacobimatrix(Q.t*I-Q.X,Q)
179179
elseif isone(Δa) && iszero(Δb) && iszero(Δc) # raising by 1
180180
cholesky_jacobimatrix(Q.X,Q)
@@ -188,7 +188,7 @@ function semiclassical_jacobimatrix(Q::SemiclassicalJacobi, a, b, c)
188188
semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b+1, Q.c, Q), a, b, c)
189189
elseif c > Q.c
190190
semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c+1, Q), a, b, c)
191-
# TODO: Implement lowering via QL or via inverting R
191+
# TODO: Implement lowering via QL/reverse Cholesky or via inverting R
192192
# elseif b < Q.b # iterative lowering by 1
193193
# semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b-1, Q.c, Q), a, b, c)
194194
# elseif c < Q.c
@@ -208,6 +208,7 @@ LanczosPolynomial(P::SemiclassicalJacobi{T}) where T =
208208
209209
gives either a mapped `Jacobi` or `LanczosPolynomial` version of `P`.
210210
"""
211+
# TODO: Use ConvertedOPs for integer special cases?
211212
toclassical(P::SemiclassicalJacobi{T}) where T = iszero(P.c) ? Normalized(jacobi(P.b, P.a, UnitInterval{T}())) : LanczosPolynomial(P)
212213

213214
copy(P::SemiclassicalJacobi) = P
@@ -224,8 +225,6 @@ function summary(io::IO, P::SemiclassicalJacobi)
224225
print(io, "SemiclassicalJacobi with weight x^$a * (1-x)^$b * ($t-x)^$c")
225226
end
226227

227-
228-
229228
jacobimatrix(P::SemiclassicalJacobi) = P.X
230229

231230
"""
@@ -260,40 +259,51 @@ function semijacobi_ldiv(P::SemiclassicalJacobi, Q)
260259
(P \ R) * _p0(R̃) * (R̃ \ Q)
261260
end
262261

262+
# returns conversion operator from SemiclassicalJacobi P to SemiclassicalJacobi Q in a single step via decomposition.
263+
function semijacobi_ldiv_direct(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
264+
(Q == P) && return SymTridiagonal(Ones(∞),Zeros(∞))
265+
Δa = Q.a-P.a
266+
Δb = Q.b-P.b
267+
Δc = Q.c-P.c
268+
M = Diagonal(Ones(∞))
269+
if iseven(Δa) && iseven(Δb) && iseven(Δc)
270+
M = qr(P.X^(Δa÷2)*(I-P.X)^(Δb÷2)*(Q.t*I-P.X)^(Δc÷2)).R
271+
return ApplyArray(*, Diagonal(sign.(view(M,band(0))).*Fill(abs.(1/M[1]),∞)), M) # match normalization choice P_0(x) = 1
272+
elseif isone(Δa) && iszero(Δb) && iszero(Δc) # special case (Δa,Δb,Δc) = (1,0,0)
273+
M = cholesky(P.X).U
274+
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M) # match normalization choice P_0(x) = 1
275+
elseif iszero(Δa) && isone(Δb) && iszero(Δc) # special case (Δa,Δb,Δc) = (0,1,0)
276+
M = cholesky(I-P.X).U
277+
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M) # match normalization choice P_0(x) = 1
278+
elseif iszero(Δa) && iszero(Δb) && isone(Δc) # special case (Δa,Δb,Δc) = (0,0,1)
279+
M = cholesky(Q.t*I-P.X).U
280+
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M) # match normalization choice P_0(x) = 1
281+
elseif isinteger(Δa) && isinteger(Δb) && isinteger(Δc)
282+
M = cholesky(Symmetric(P.X^(Δa)*(I-P.X)^(Δb)*(Q.t*I-P.X)^(Δc))).U
283+
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M) # match normalization choice P_0(x) = 1
284+
else
285+
error("Implement")
286+
end
287+
end
288+
263289
# returns conversion operator from SemiclassicalJacobi P to SemiclassicalJacobi Q.
264290
function semijacobi_ldiv(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
265291
@assert Q.t P.t
266-
# For polynomial modifications, use Cholesky/QR. Use Lanzos otherwise.
267-
M = Diagonal(Ones(∞))
268-
(Q == P) && return M
292+
(Q == P) && return SymTridiagonal(Ones(∞),Zeros(∞))
269293
Δa = Q.a-P.a
270294
Δb = Q.b-P.b
271295
Δc = Q.c-P.c
272-
if iseven(Δa) && iseven(Δb) && iseven(Δc)
273-
if !iszero(Δa)
274-
M = ApplyArray(*,M,P.X^(Δa÷2))
275-
end
276-
if !iszero(Δb)
277-
M = ApplyArray(*,M,(I-P.X)^(Δb÷2))
278-
end
279-
if !iszero(Δc)
280-
M = ApplyArray(*,M,(Q.t*I-P.X)^(Δc÷2))
281-
end
282-
M = qr(M).R
283-
return ApplyArray(*, Diagonal(sign.(view(M,band(0))).*Fill(abs.(1/M[1]),∞)), M) # match our normalization choice P_0(x) = 1
284-
elseif isinteger(Δa) && isinteger(Δb) && isinteger(Δc)
285-
if !iszero(Δa)
286-
M = ApplyArray(*,M,P.X^(Δa))
287-
end
288-
if !iszero(Δb)
289-
M = ApplyArray(*,M,(I-P.X)^(Δb))
290-
end
291-
if !iszero(Δc)
292-
M = ApplyArray(*,M,(Q.t*I-P.X)^(Δc))
296+
if isinteger(Δa) && isinteger(Δb) && isinteger(Δc) # (Δa,Δb,Δc) are integers -> use QR/Cholesky iteratively
297+
if ((isone(Δa)||isone(Δa/2)) && iszero(Δb) && iszero(Δc)) || (iszero(Δa) && (isone(Δb)||isone(Δb/2)) && iszero(Δc)) || (iszero(Δa) && iszero(Δb) && (isone(Δc)||isone(Δc/2)))
298+
M = semijacobi_ldiv_direct(Q, P)
299+
elseif Δa > 0 # iterative modification by 1
300+
M = ApplyArray(*,semijacobi_ldiv_direct(Q, SemiclassicalJacobi(Q.t, Q.a-1-iseven(Δa), Q.b, Q.c)),semijacobi_ldiv(SemiclassicalJacobi(Q.t, Q.a-1-iseven(Δa), Q.b, Q.c), P))
301+
elseif Δb > 0
302+
M = ApplyArray(*,semijacobi_ldiv_direct(Q, SemiclassicalJacobi(Q.t, Q.a, Q.b-1-iseven(Δb), Q.c)),semijacobi_ldiv(SemiclassicalJacobi(Q.t, Q.a, Q.b-1-iseven(Δb), Q.c), P))
303+
elseif Δc > 0
304+
M = ApplyArray(*,semijacobi_ldiv_direct(Q, SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c-1-iseven(Δc))),semijacobi_ldiv(SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c-1-iseven(Δc)), P))
293305
end
294-
M = cholesky(Symmetric(M)).U
295-
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M) # match our normalization choice P_0(x) = 1
296-
else # fallback option for non-integer weight modification
306+
else # fallback to Lancos
297307
R = SemiclassicalJacobi(P.t, mod(P.a,-1), mod(P.b,-1), mod(P.c,-1))
298308
= toclassical(R)
299309
return (P \ R) * _p0(R̃) * (R̃ \ Q)
@@ -338,8 +348,14 @@ function \(w_A::WeightedSemiclassicalJacobi, w_B::WeightedSemiclassicalJacobi)
338348
wA,A = w_A.args
339349
wB,B = w_B.args
340350
@assert wA.t == wB.t == A.t == B.t
341-
342-
if wA.a == wB.a && wA.b == wB.b && wA.c == wB.c
351+
Δa = B.a-A.a
352+
Δb = B.b-A.b
353+
Δc = B.c-A.c
354+
355+
if (wA.a == A.a) && (wA.b == A.b) && (wA.c == A.c) && (wB.a == B.a) && (wB.b == B.b) && (wB.c == B.c) && isinteger(A.a) && isinteger(A.b) && isinteger(A.c) && isinteger(B.a) && isinteger(B.b) && isinteger(B.c)
356+
k = (A \ SemiclassicalJacobiWeight(A.t,Δa,Δb,Δc))[1]
357+
return (ApplyArray(*,Diagonal(Fill(k,∞)),(B \ A)))'
358+
elseif wA.a == wB.a && wA.b == wB.b && wA.c == wB.c # fallback to Christoffel–Darboux
343359
A \ B
344360
elseif wA.a+1 == wB.a && wA.b == wB.b && wA.c == wB.c
345361
@assert A.a+1 == B.a && A.b == B.b && A.c == B.c
@@ -383,29 +399,30 @@ massmatrix(P::SemiclassicalJacobi) = Diagonal(Fill(sum(orthogonalityweight(P)),
383399
end
384400

385401
function ldiv(Q::SemiclassicalJacobi, f::AbstractQuasiVector)
386-
if iszero(Q.a) && iszero(Q.b) && Q.c == -one(eltype(Q.t))
387-
# TODO: due to a stdlib error this won't work with bigfloat as is
388-
T = typeof(Q.t)
402+
T = typeof(Q.t)
403+
if iszero(Q.a) && iszero(Q.b) && isone(-Q.c) # (0,0,-1) special case
389404
R = legendre(zero(T)..one(T))
390405
B = neg1c_tolegendre(Q.t)
391406
return (B \ (R \ f))
407+
elseif isinteger(Q.a) && isinteger(Q.b) && isinteger(Q.c) # (a,b,c) are integers -> use QR/Cholesky
408+
= Normalized(jacobi(Q.b, Q.a, UnitInterval{T}()))
409+
return (Q \ SemiclassicalJacobi(Q.t, Q.a, Q.b, 0)) * _p0(R̃) * (R̃ \ f)
410+
else # fallback to Lanzcos
411+
= toclassical(SemiclassicalJacobi(Q.t, mod(Q.a,-1), mod(Q.b,-1), mod(Q.c,-1)))
412+
return (Q \ R̃) * (R̃ \ f)
392413
end
393-
R = SemiclassicalJacobi(Q.t, mod(Q.a,-1), mod(Q.b,-1), mod(Q.c,-1))
394-
= toclassical(R)
395-
return (Q \ R̃) * (R̃ \ f)
396414
end
397415
function ldiv(Qn::SubQuasiArray{<:Any,2,<:SemiclassicalJacobi,<:Tuple{<:Inclusion,<:Any}}, C::AbstractQuasiArray)
398416
_,jr = parentindices(Qn)
399417
Q = parent(Qn)
400418
if iszero(Q.a) && iszero(Q.b) && Q.c == -one(eltype(Q.t))
401-
# TODO: due to a stdlib error this won't work with bigfloat as is
402419
T = typeof(Q.t)
403420
R = legendre(zero(T)..one(T))
404421
B = neg1c_tolegendre(Q.t)
405422
return (B[jr,jr] \ (R[:,jr] \ C))
406423
end
407-
R = SemiclassicalJacobi(Q.t, mod(Q.a,-1), mod(Q.b,-1), mod(Q.c,-1))
408-
= toclassical(R)
424+
425+
= toclassical(SemiclassicalJacobi(Q.t, mod(Q.a,-1), mod(Q.b,-1), mod(Q.c,-1)))
409426
(Q \ R̃)[jr,jr] * (R̃[:,jr] \ C)
410427
end
411428

src/neg1c.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
######
55

66
# compute n-th coefficient from direct evaluation formula
7-
αdirect(n, t::T) where T = 2*n*_₂F₁((one(T)+n)/2,(n+2)/2,(2*n+3)/2,1/t^2)/(t*2*(1+2*n)*_₂F₁(n/2,(n+one(T))/2,(2*n+one(T))/2,1/t^2))
7+
function αdirect(n, tt::T) where T
8+
t = big(tt)
9+
return convert(T,2*n*_₂F₁((one(T)+n)/2,(n+2)/2,(2*n+3)/2,1/t^2)/(t*2*(1+2*n)*_₂F₁(n/2,(n+one(T))/2,(2*n+one(T))/2,1/t^2)))
10+
end
811

912
# inital value n=0 for α_{n,n-1}(t) coefficients
1013
initialα(t) = t-2/(log1p(t)-log(t-1))

src/twoband.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,14 @@ end
185185

186186
function divmul(R::TwoBandJacobi, D::Derivative, P::TwoBandJacobi)
187187
T = promote_type(eltype(R), eltype(P))
188-
ρ=convert(T,R.ρ); t=inv(one(T)-ρ^2)
188+
ρ = convert(T,R.ρ); t=inv(one(T)-ρ^2)
189189
x = axes(R.Q, 1)
190190

191191
Dₑ = -T(2) * t .* (R.Q \ (Derivative(x)*P.P))
192192
Dₒ = -T(2) .* (HalfWeighted{:c}(R.P) \ (Derivative(x)* HalfWeighted{:c}(P.Q)))
193-
194-
d = Dₑ.args[1] .* Dₑ.args[2].parent.data
193+
d = Dₑ.args[1][1,1] .* Dₑ.args[2].parent.data
195194
(dₑ, dlₑ) = d[1,:], d[2, :]
196-
(dₒ, dlₒ) = Dₒ.data[1,:], Dₒ.data[2,:]
195+
(dₒ, dlₒ) = Vcat(-one(T),Dₒ[band(1)]), Dₒ[band(0)]
197196
BandedMatrix(1=>Interlace(dlₒ, -dₑ), 3=>Interlace(-dₒ[2:∞],dlₑ))
198197
end
199198

@@ -205,7 +204,7 @@ function divmul(R::TwoBandJacobi, D::Derivative, HP::HalfWeighted{:ab,<:Any,<:Tw
205204
Dₑ = -2*(one(T)-ρ^2) .* (R.Q \ (Derivative(axes(R.Q,1))*HalfWeighted{:ab}(HP.P.P)))
206205
D₀ = -2*(one(T)-ρ^2)^2 .* (Weighted(R.P) \ (Derivative(axes(R.P,1))*Weighted(HP.P.Q)))
207206

208-
(dₑ, dlₑ, d₀, dl₀) = Dₑ.data[1,:], Dₑ.data[2,:], D₀.data[1,:], D₀.data[2,:]
207+
(dₑ, dlₑ, d₀, dl₀) = Dₑ[band(0)], Dₑ[band(-1)], D₀[band(-1)], D₀[band(-2)]
209208
BandedMatrix(-1=>Interlace(dₑ, -d₀), -3=>Interlace(-dlₑ, dl₀))
210209
end
211210

test/runtests.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ end
5959
@test w == jacobiexpansion(w)
6060
end
6161

62+
@testset "Evaluations and Type declaration" begin
63+
@test SemiclassicalJacobi(1.1,0,0,4)[0.2, 1] SemiclassicalJacobi{Float64}(1.1,0,0,4)[0.2, 1]
64+
@test SemiclassicalJacobi(1.013,0,0,4)[0.1993, 3] SemiclassicalJacobi{Float64}(1.013,0,0,4)[0.1993, 3]
65+
@test SemiclassicalJacobi(1.013,2,2,2.3)[0.7, 4] SemiclassicalJacobi{Float64}(1.013,2,2,2.3)[0.7, 4]
66+
end
67+
6268
@testset "Half-range Chebyshev" begin
6369
@testset "T and W" begin
6470
T = SemiclassicalJacobi(2, -1/2, 0, -1/2)
@@ -469,6 +475,39 @@ end
469475
end
470476
end
471477

478+
@testset "Weighted Conversion" begin
479+
@testset "low c" begin
480+
P = SemiclassicalJacobi(1.1,0,0,10)
481+
Q = SemiclassicalJacobi(1.1,1,1,10)
482+
R = Q \ P
483+
L = Weighted(P) \ Weighted(Q)
484+
C = R./L'
485+
@test C[1,1] C[5,5] C[8,8]
486+
487+
P = SemiclassicalJacobi(1.6,0,0,10)
488+
Q = SemiclassicalJacobi(1.6,2,2,10)
489+
R = Q \ P
490+
L = Weighted(P) \ Weighted(Q)
491+
C = R./L'
492+
@test C[1,1] C[5,5] C[8,8]
493+
494+
P = SemiclassicalJacobi(1.1213,0,0,10)
495+
Q = SemiclassicalJacobi(1.1213,2,3,10)
496+
R = Q \ P
497+
L = Weighted(P) \ Weighted(Q)
498+
C = R./L'
499+
@test C[1,1] C[5,5] C[8,8]
500+
end
501+
@testset "high c" begin
502+
P = SemiclassicalJacobi(1.1,0,0,100)
503+
Q = SemiclassicalJacobi(1.1,1,1,100)
504+
R = Q \ P
505+
L = Weighted(P) \ Weighted(Q)
506+
C = R./L'
507+
@test C[1,1] C[5,5] C[8,8]
508+
end
509+
end
510+
472511
@testset "Conversion operators via decomposition" begin
473512
t = 1.2
474513
P = SemiclassicalJacobi(t, 1, 0, 1)

0 commit comments

Comments
 (0)