Skip to content

Commit a1e4c3e

Browse files
authored
Implement b=-1 (#111)
* temp commit * stash * Put current changes in * Update SemiclassicalOrthogonalPolynomials.jl * other changes * current state * Fix expansions * Finally fix all the tests. Fixed issue with expansions with integer coefficients * Add comment to address later [no ci] * Fix connections * Testing is a bit excessive. Trims the time down from 14m (why do three parameters take that long) to ~1m * Properly test differentiation [no ci] * Fix weighted expansions * Weighted derivatives come for free. * Remove old comments and show [no ci] * Fix expansions * Add a comment explaining the expansion code
1 parent 5011dc4 commit a1e4c3e

File tree

4 files changed

+346
-32
lines changed

4 files changed

+346
-32
lines changed

src/SemiclassicalOrthogonalPolynomials.jl

+82-17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import InfiniteArrays: OneToInf, InfUnitRange
1818
import ContinuumArrays: basis, Weight, @simplify, AbstractBasisLayout, BasisLayout, MappedBasisLayout, grid, plotgrid, equals_layout, ExpansionLayout
1919
import FillArrays: SquareEye
2020
import HypergeometricFunctions: _₂F₁general2
21+
import InfiniteLinearAlgebra: BidiagonalConjugation
2122

2223
export LanczosPolynomial, Legendre, Normalized, normalize, SemiclassicalJacobi, SemiclassicalJacobiWeight, WeightedSemiclassicalJacobi, OrthogonalPolynomialRatio
2324

@@ -139,18 +140,26 @@ function semiclassical_jacobimatrix(t, a, b, c)
139140
C = -(N)./(N.*4 .- 2)
140141
B = Vcat((α[1]^2*3-α[1]*α[2]*2-1)/6 , -(N)./(N.*4 .+ 2).*α[2:end]./α)
141142
return SymTridiagonal(A, sqrt.(B.*C)) # if J is Tridiagonal(c,a,b) then for norm. OPs it becomes SymTridiagonal(a, sqrt.( b.* c))
142-
end
143-
P = Normalized(jacobi(b, a, UnitInterval{T}()))
144-
iszero(c) && return jacobimatrix(P)
145-
if isone(c)
146-
return cholesky_jacobimatrix(Symmetric(P \ ((t.-axes(P,1)).*P)), P)
147-
elseif c == 2
148-
return qr_jacobimatrix(Symmetric(P \ ((t.-axes(P,1)).*P)), P)
149-
elseif isinteger(c) && c 0 # reduce other integer c cases to hierarchy
150-
return SemiclassicalJacobi.(t, a, b, 0:Int(c))[end].X
151-
else # if c is not an integer, use Lanczos
152-
x = axes(P,1)
153-
return jacobimatrix(LanczosPolynomial(@.(x^a * (1-x)^b * (t-x)^c), jacobi(b, a, UnitInterval{T}())))
143+
elseif b == -one(T)
144+
J′ = semiclassical_jacobimatrix(t, a, one(b), c)
145+
J′a, J′b = diagonaldata(J′), supdiagonaldata(J′)
146+
A = Vcat(one(T), J′a[1:end])
147+
B = Vcat(-one(T), J′b[1:end])
148+
C = Vcat(zero(T), J′b[1:end])
149+
return Tridiagonal(B, A, C)
150+
else
151+
P = Normalized(jacobi(b, a, UnitInterval{T}()))
152+
iszero(c) && return jacobimatrix(P)
153+
if isone(c)
154+
return cholesky_jacobimatrix(Symmetric(P \ ((t.-axes(P,1)).*P)), P)
155+
elseif isone(c/2)
156+
return qr_jacobimatrix(Symmetric(P \ ((t.-axes(P,1)).*P)), P)
157+
elseif isinteger(c) && c 0 # reduce other integer c cases to hierarchy
158+
return SemiclassicalJacobi.(t, a, b, 0:Int(c))[end].X
159+
else # if c is not an integer, use Lanczos
160+
x = axes(P,1)
161+
return jacobimatrix(LanczosPolynomial(@.(x^a * (1-x)^b * (t-x)^c), jacobi(b, a, UnitInterval{T}())))
162+
end
154163
end
155164
end
156165

@@ -162,11 +171,16 @@ function semiclassical_jacobimatrix(Q::SemiclassicalJacobi, a, b, c)
162171
# special cases
163172
if iszero(a) && iszero(b) && c == -one(eltype(Q.t)) # (a,b,c) = (0,0,-1) special case
164173
return semiclassical_jacobimatrix(Q.t, zero(Q.t), zero(Q.t), c)
174+
elseif iszero(Δa) && iszero(Δc) && Δb == 2 && b == 1
175+
# When going from P[t, a, -1, c] to P[t, a, 1, c], you can just take
176+
return SymTridiagonal(Q.X.d[2:end], Q.X.du[2:end])
165177
elseif iszero(c) # classical Jacobi polynomial special case
166178
return jacobimatrix(Normalized(jacobi(b, a, UnitInterval{eltype(Q.t)}())))
167179
elseif iszero(Δa) && iszero(Δb) && iszero(Δc) # same basis
168180
return Q.X
169-
end
181+
elseif b == -one(eltype(Q.t))
182+
return semiclassical_jacobimatrix(Q.t, a, b, c)
183+
end
170184

171185
if isone(Δa/2) && iszero(Δb) && iszero(Δc) # raising by 2
172186
qr_jacobimatrix(Q.X,Q)
@@ -408,7 +422,35 @@ function copy(L::Ldiv{SemiclassicalJacobiLayout,SemiclassicalJacobiLayout})
408422
(inv(M_Q) * L') * M_P
409423
end
410424

411-
\(A::SemiclassicalJacobi, B::SemiclassicalJacobi) = semijacobi_ldiv(A, B)
425+
function \(A::SemiclassicalJacobi, B::SemiclassicalJacobi{T}) where {T}
426+
if A.b == -1 && B.b -1
427+
return UpperTriangular(ApplyArray(inv, B \ A))
428+
elseif B.b == -1 && A.b -1
429+
# First convert Bᵗᵃ⁻¹ᶜ into Bᵗᵃ⁰ᶜ
430+
Bᵗᵃ⁰ᶜ = SemiclassicalJacobi(B.t, B.a, zero(B.b), B.c, A)
431+
Bᵗᵃ¹ᶜ = SemiclassicalJacobi(B.t, B.a, one(B.a), B.c, A)
432+
Rᵦₐ₁ᵪᵗᵃ⁰ᶜ = Weighted(Bᵗᵃ⁰ᶜ) \ Weighted(Bᵗᵃ¹ᶜ)
433+
b1 = Rᵦₐ₁ᵪᵗᵃ⁰ᶜ[band(0)]
434+
b0 = Vcat(one(T), Rᵦₐ₁ᵪᵗᵃ⁰ᶜ[band(-1)])
435+
Rᵦₐ₋₁ᵪᵗᵃ⁰ᶜ = Bidiagonal(b0, b1, :U)
436+
# Then convert Bᵗᵃ⁰ᶜ into A and complete
437+
Rₐ₀ᵪᴬ = UpperTriangular(A \ Bᵗᵃ⁰ᶜ)
438+
return ApplyArray(*, Rₐ₀ᵪᴬ, Rᵦₐ₋₁ᵪᵗᵃ⁰ᶜ)
439+
elseif A.b == B.b == -1
440+
Bᵗᵃ¹ᶜ = SemiclassicalJacobi(B.t, B.a, one(B.b), B.c, B)
441+
Aᵗᵃ¹ᶜ = SemiclassicalJacobi(A.t, A.a, one(A.b), A.c, A)
442+
Rₐ₁ᵪᵗᵘ¹ᵛ = Aᵗᵃ¹ᶜ \ Bᵗᵃ¹ᶜ
443+
# Make 1 ⊕ Rₐ₁ᵪᵗᵘ¹ᵛ
444+
V = eltype(Rₐ₁ᵪᵗᵘ¹ᵛ)
445+
Rₐ₋₁ᵪᵗᵘ⁻¹ᵛ = Vcat(
446+
Hcat(one(V), Zeros{V}(1, ∞)),
447+
Hcat(Zeros{V}(∞), Rₐ₁ᵪᵗᵘ¹ᵛ)
448+
)
449+
return Rₐ₋₁ᵪᵗᵘ⁻¹ᵛ
450+
else
451+
return semijacobi_ldiv(A, B)
452+
end
453+
end
412454
\(A::LanczosPolynomial, B::SemiclassicalJacobi) = semijacobi_ldiv(A, B)
413455
\(A::SemiclassicalJacobi, B::LanczosPolynomial) = semijacobi_ldiv(A, B)
414456
function \(w_A::WeightedSemiclassicalJacobi{T}, w_B::WeightedSemiclassicalJacobi{T}) where T
@@ -457,6 +499,23 @@ end
457499

458500
\(w_A::Weighted{<:Any,<:SemiclassicalJacobi}, w_B::Weighted{<:Any,<:SemiclassicalJacobi}) = convert(WeightedBasis, w_A) \ convert(WeightedBasis, w_B)
459501

502+
function \(w_A::HalfWeighted{lr, T, <:SemiclassicalJacobi}, B::AbstractQuasiArray{V}) where {lr, T, V}
503+
WP = convert(WeightedBasis, w_A)
504+
w_A.P.b -1 && return WP \ B # no need to special case here
505+
!iszero(WP.args[1].b) && throw(ArgumentError("Cannot expand in a weighted basis including 1/(1-x)."))
506+
# To expand f(x) = w(x)P(x)𝐟, note that P = [1 (1-x)Q] so
507+
# f(x) = w(x)[1 (1-x)Q(x)][f₀; 𝐟₁] = w(x)f₀ + w(x)(1-x)Q(x)𝐟₁. Thus,
508+
# f(1) = w(1)f₀ ⟹ f₀ = f(1) / w(1)
509+
# Then, f(x) - w(x)f₀ = w(x)(1-x)Q(x)𝐟₁, so that 𝐟₁ is just the expansion of
510+
# f(x) - w(x)f₀ in the w(x)(1-x)Q(x) basis.
511+
w, P = WP.args
512+
f₀ = B[end] / w[end]
513+
C = B - w * f₀
514+
Q = SemiclassicalJacobiWeight(w.t, w.a, one(w.b), w.c) .* SemiclassicalJacobi(P.t, P.a, one(P.b), P.c, P)
515+
f = Q \ C
516+
return Vcat(f₀, f)
517+
end
518+
460519
weightedgrammatrix(P::SemiclassicalJacobi) = Diagonal(Fill(sum(orthogonalityweight(P)),∞))
461520

462521
@simplify function *(Ac::QuasiAdjoint{<:Any,<:SemiclassicalJacobi}, wB::WeightedBasis{<:Any,<:SemiclassicalJacobiWeight,<:SemiclassicalJacobi})
@@ -472,9 +531,11 @@ function ldiv(Q::SemiclassicalJacobi, f::AbstractQuasiVector)
472531
R = legendre(zero(T)..one(T))
473532
B = neg1c_tolegendre(Q.t)
474533
return (B \ (R \ f))
475-
elseif isinteger(Q.a) && isinteger(Q.b) && isinteger(Q.c) # (a,b,c) are integers -> use QR/Cholesky
534+
elseif isinteger(Q.a) && (isinteger(Q.b) && Q.b 0) && isinteger(Q.c) # (a,b,c) are integers -> use QR/Cholesky
476535
= Normalized(jacobi(Q.b, Q.a, UnitInterval{T}()))
477536
return (Q \ SemiclassicalJacobi(Q.t, Q.a, Q.b, 0)) * _p0(R̃) * (R̃ \ f)
537+
elseif isinteger(Q.a) && isone(-Q.b) && isinteger(Q.c)
538+
return semijacobi_ldiv(Q, f) # jacobi(< 0, Q.a) fails in the method above. jacobi(-1, 0) also leads to NaNs in coefficients
478539
else # fallback to Lanzcos
479540
= toclassical(SemiclassicalJacobi(Q.t, mod(Q.a,-1), mod(Q.b,-1), mod(Q.c,-1)))
480541
return (Q \ R̃) * (R̃ \ f)
@@ -530,7 +591,7 @@ mutable struct SemiclassicalJacobiFamily{T, A, B, C} <: AbstractCachedVector{Sem
530591
datasize::Tuple{Int}
531592
end
532593

533-
isnormalized(::SemiclassicalJacobi) = true
594+
isnormalized(J::SemiclassicalJacobi) = J.b -1 # there is no normalisation for b == -1
534595
size(P::SemiclassicalJacobiFamily) = (max(length(P.a), length(P.b), length(P.c)),)
535596

536597
_checkrangesizes() = ()
@@ -572,8 +633,12 @@ _broadcast_getindex(a::Number,k) = a
572633

573634
function LazyArrays.cache_filldata!(P::SemiclassicalJacobiFamily, inds::AbstractUnitRange)
574635
t,a,b,c = P.t,P.a,P.b,P.c
636+
isrange = P.b isa AbstractUnitRange
575637
for k in inds
576-
P.data[k] = SemiclassicalJacobi(t, _broadcast_getindex(a,k), _broadcast_getindex(b,k), _broadcast_getindex(c,k), P.data[k-2])
638+
# If P.data[k-2] is not normalised (aka b = -1), cholesky fails. With the current design, this is only a problem if P.b
639+
# is a range since we can translate between polynomials that both have b = -1.
640+
Pprev = (isrange && P.b[k-2] == -1) ? P.data[k-1] : P.data[k-2] # isrange && P.b[k-2] == -1 could also be !isnormalized(P.data[k-2])
641+
P.data[k] = SemiclassicalJacobi(t, _broadcast_getindex(a,k), _broadcast_getindex(b,k), _broadcast_getindex(c,k), Pprev)
577642
end
578643
P
579644
end

src/derivatives.jl

+27-14
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,27 @@ function divdiff(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
4444
_BandedMatrix(Vcat(((1:∞) .* d)', (((1:∞) .* (v1 .+ B[2:end]./A[2:end]) .- (2:∞) .*.* v2 .+ β ./ α)) .* v3)'), ∞, 2,-1)'
4545
end
4646

47-
function diff(P::SemiclassicalJacobi; dims=1)
48-
Q = SemiclassicalJacobi(P.t, P.a+1,P.b+1,P.c+1,P)
49-
Q * divdiff(Q, P)
47+
function diff(P::SemiclassicalJacobi{T}; dims=1) where {T}
48+
if P.b -1
49+
Q = SemiclassicalJacobi(P.t, P.a+1,P.b+1,P.c+1,P)
50+
Q * divdiff(Q, P)
51+
elseif P.b == -1
52+
Pᵗᵃ⁰ᶜ = SemiclassicalJacobi(P.t, P.a, zero(P.b), P.c)
53+
Pᵗᵃ¹ᶜ = SemiclassicalJacobi(P.t, P.a, one(P.b), P.c, Pᵗᵃ⁰ᶜ)
54+
Rᵦₐ₁ᵪᵗᵃ⁰ᶜ = Weighted(Pᵗᵃ⁰ᶜ) \ Weighted(Pᵗᵃ¹ᶜ)
55+
Dₐ₀ᵪᵃ⁺¹¹ᶜ⁺¹ = diff(Pᵗᵃ⁰ᶜ)
56+
Pᵗᵃ⁺¹¹ᶜ⁺¹ = Dₐ₀ᵪᵃ⁺¹¹ᶜ⁺¹.args[1]
57+
Pᵗᵃ⁺¹⁰ᶜ⁺¹ = SemiclassicalJacobi(P.t, P.a + 1, zero(P.b), P.c + 1, Pᵗᵃ⁰ᶜ)
58+
Rₐ₊₁₀ᵪ₊₁ᵗᵃ⁺¹¹ᶜ⁺¹ = Pᵗᵃ⁺¹¹ᶜ⁺¹ \ Pᵗᵃ⁺¹⁰ᶜ⁺¹
59+
Dₐ₋₁ᵪᵃ⁺¹⁰ᶜ⁺¹ = BidiagonalConjugation(Rₐ₊₁₀ᵪ₊₁ᵗᵃ⁺¹¹ᶜ⁺¹, coefficients(Dₐ₀ᵪᵃ⁺¹¹ᶜ⁺¹), Rᵦₐ₁ᵪᵗᵃ⁰ᶜ, 'U')
60+
b2 = Vcat(zero(T), zero(T), supdiagonaldata(Dₐ₋₁ᵪᵃ⁺¹⁰ᶜ⁺¹))
61+
b1 = Vcat(zero(T), diagonaldata(Dₐ₋₁ᵪᵃ⁺¹⁰ᶜ⁺¹))
62+
data = Hcat(b2, b1)'
63+
D = _BandedMatrix(data, ∞, -1, 2)
64+
return Pᵗᵃ⁺¹⁰ᶜ⁺¹ * D
65+
end
5066
end
5167

52-
53-
54-
5568
function divdiff(wP::Weighted{<:Any,<:SemiclassicalJacobi}, wQ::Weighted{<:Any,<:SemiclassicalJacobi})
5669
Q,P = wQ.P,wP.P
5770
((-sum(orthogonalityweight(Q))/sum(orthogonalityweight(P))) * (Q \ diff(P))')
@@ -80,8 +93,8 @@ function divdiff(HQ::HalfWeighted{:a,<:Any,<:SemiclassicalJacobi}, HP::HalfWeigh
8093

8194
_BandedMatrix(
8295
Vcat(
83-
((a:∞) .* v2 .- ((a+1):∞) .* Vcat(1,v1[2:end] .* d))',
84-
(((a+1):∞) .* Vcat(1,d))'), ℵ₀, 0,1)
96+
((a:∞) .* v2 .- ((a+1):∞) .* Vcat(1,v1[2:end] .* d))',
97+
(((a+1):∞) .* Vcat(1,d))'), ℵ₀, 0,1)
8598
end
8699

87100
function divdiff(HQ::HalfWeighted{:b,<:Any,<:SemiclassicalJacobi}, HP::HalfWeighted{:b,<:Any,<:SemiclassicalJacobi})
@@ -98,8 +111,8 @@ function divdiff(HQ::HalfWeighted{:b,<:Any,<:SemiclassicalJacobi}, HP::HalfWeigh
98111

99112
_BandedMatrix(
100113
Vcat(
101-
(-(b:∞) .* v2 .+ ((b+1):∞) .* Vcat(1,v1[2:end] .* d) .+ Vcat(0,(1:∞) .* d2))',
102-
(-((b+1):∞) .* Vcat(1,d))'), ℵ₀, 0,1)
114+
(-(b:∞) .* v2 .+ ((b+1):∞) .* Vcat(1,v1[2:end] .* d) .+ Vcat(0,(1:∞) .* d2))',
115+
(-((b+1):∞) .* Vcat(1,d))'), ℵ₀, 0,1)
103116
end
104117

105118
function divdiff(HQ::HalfWeighted{:c,<:Any,<:SemiclassicalJacobi}, HP::HalfWeighted{:c,<:Any,<:SemiclassicalJacobi})
@@ -115,8 +128,8 @@ function divdiff(HQ::HalfWeighted{:c,<:Any,<:SemiclassicalJacobi}, HP::HalfWeigh
115128
v2 = MulAddAccumulate(Vcat(0,0,A[2:∞] ./ α), Vcat(0,B[1], B[2:end] .* d))
116129
_BandedMatrix(
117130
Vcat(
118-
(-(c:∞) .* v2 .+ ((c+1):∞) .* Vcat(1,v1[2:end] .* d) .+ Vcat(0,(t:t:∞) .* d2))',
119-
(-((c+1):∞) .* Vcat(1,d))'), ℵ₀, 0,1)
131+
(-(c:∞) .* v2 .+ ((c+1):∞) .* Vcat(1,v1[2:end] .* d) .+ Vcat(0,(t:t:∞) .* d2))',
132+
(-((c+1):∞) .* Vcat(1,d))'), ℵ₀, 0,1)
120133
end
121134

122135
function diff(HP::HalfWeighted{:a,<:Any,<:SemiclassicalJacobi}; dims=1)
@@ -156,7 +169,7 @@ function divdiff(HQ::HalfWeighted{:ab}, HP::HalfWeighted{:ab})
156169
f = MulAddAccumulate(Vcat(0,0,A[2:end] ./ α[2:end]), Vcat(0, (B./ α) .* e))
157170
g = cumsum./ α)
158171
_BandedMatrix(Vcat((((a+1):∞) .* e .- ((b+a+1):∞).*f .+ ((a+b+2):∞) .* e .* g )',
159-
(-((a+b+2):∞) .* d)'),ℵ₀,1,0)
172+
(-((a+b+2):∞) .* d)'),ℵ₀,1,0)
160173
end
161174

162175

@@ -173,7 +186,7 @@ function divdiff(HQ::HalfWeighted{:bc}, HP::HalfWeighted{:bc})
173186
f = MulAddAccumulate(Vcat(0,0,A[2:end] ./ α[2:end]), Vcat(0, (B./ α) .* e))
174187
g = cumsum./ α)
175188
_BandedMatrix(Vcat((-((t+1)* (0:∞) .+ (t*(b+1) + c+1)) .* e .+ ((c+b+1):∞).*f .- ((b+c+2):∞) .* e .* g )',
176-
(((b+c+2):∞) .* d)'),ℵ₀,1,0)
189+
(((b+c+2):∞) .* d)'),ℵ₀,1,0)
177190
end
178191

179192
function divdiff(HQ::HalfWeighted{:ac}, HP::HalfWeighted{:ac})

test/runtests.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -675,4 +675,4 @@ end
675675

676676
include("test_derivative.jl")
677677
include("test_neg1c.jl")
678-
678+
include("test_neg1b.jl")

0 commit comments

Comments
 (0)